for-linus-2022080201

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2
 
 iQIVAwUAYul6baZi849r7WBJAQLexhAAugWU2nqiJOAS/YDAoDdHVIdQR0dqHQ8K
 ckJKyTQrsjPq0h1r0EmuH6kiJ3WoZi8iHssgQrffneewXiEhpdSGBQqEF72j+z2u
 FuCk1rjLABu0f/WLkmDDN1a8CMhNj+dDuT3WDdUkWD8TMYWKa1tPb2oMKRZpnyqa
 tdbjLbia7NCXN26znry95Rc/16JpwHpJ5EVxzkrcNQs7KunMK71CziPbW0CePw9o
 9LQYIt7FhhnbtY2m+O4rkShct8ntAAaR4lUdR+GISXHkm7PfzBtpkA8Re97TQd0Y
 qA6wYX4JhwVf9dJBmAbDvzRT10m/T4j3FbfQDeXVCaKEDxmXRZnMG2mCyhauYc9A
 YzK48MLHY8vBiPc0Le0whiLWivj36kKfbXHCTLb5AcraPftNwnXEMv4ekuCyHVrv
 2oLPtkbeLfNE5nks0k5M9K62XWebiaNmxzg5+rtCws0Z7YeuXLoRk7fbROj7iOh5
 NTEZq3Ig/+4MNmafUhhOOhP76JxvKWi9tpw8kpAxAb+5vQJwtMyWu2k4tmNtwuwH
 J6EScD+8/rbXfEromkHyp0ZJNW4QjJ4uta9ZSvvCEAyHoQ8cfTNfwh0mrtK80cZW
 GfAFPsI/IgE6G9f97R7rqfsrE8qIjZ8EFLMcHM9QBYR42E8KuNpqxivALeO2VYUw
 M+4wttJkwy4=
 =Mv0b
 -----END PGP SIGNATURE-----

Merge tag 'for-linus-2022080201' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid

Pull HID updates from Jiri Kosina:

 - support for AMD SOCs using SFH1.1 memory access (Basavaraj Natikar)

 - XP-PEN Deco L support (José Expósito)

 - support for Elan eKTH6915 touchscreens (Douglas Anderson)

 - other small assorted fixes and device ID additions

* tag 'for-linus-2022080201' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (39 commits)
  HID: amd_sfh: Handle condition of "no sensors"
  HID: amd_sfh: Fix implicit declaration error on i386
  HID: apple: Add "GANSS" to the non-Apple list
  HID: alps: Declare U1_UNICORN_LEGACY support
  HID: wacom: Force pen out of prox if no events have been received in a while
  HID: nintendo: Add missing array termination
  HID: lg-g15: Fix comment typo
  HID: amd_sfh: Implement SFH1.1 functionality
  HID: amd_sfh: Move interrupt handling to common interface
  HID: amd_sfh: Move amd_sfh_work to common interface
  HID: amd_sfh: Move global functions to static
  HID: amd_sfh: Add remove operation in amd_mp2_ops
  HID: amd_sfh: Add PM operations in amd_mp2_ops
  HID: amd_sfh: Add descriptor operations in amd_mp2_ops
  HID: amd_sfh: Move request_list variable to client data
  HID: amd_sfh: Move request_list struct to header file
  HID: amd_sfh: Move common macros and structures
  HID: amd_sfh: Add NULL check for hid device
  HID: core: remove unneeded assignment in hid_process_report()
  ID: intel-ish-hid: hid-client: drop unexpected word "the" in the comments
  ...
This commit is contained in:
Linus Torvalds 2022-08-04 18:04:28 -07:00
commit 8d9420ca9b
42 changed files with 2065 additions and 207 deletions

View File

@ -0,0 +1,65 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/input/elan,ekth6915.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Elan eKTH6915 touchscreen controller
maintainers:
- Douglas Anderson <dianders@chromium.org>
description:
Supports the Elan eKTH6915 touchscreen controller.
This touchscreen controller uses the i2c-hid protocol with a reset GPIO.
properties:
compatible:
items:
- const: elan,ekth6915
reg:
const: 0x10
interrupts:
maxItems: 1
reset-gpios:
description: Reset GPIO; not all touchscreens using eKTH6915 hook this up.
vcc33-supply:
description: The 3.3V supply to the touchscreen.
vccio-supply:
description:
The IO supply to the touchscreen. Need not be specified if this is the
same as the 3.3V supply.
required:
- compatible
- reg
- interrupts
- vcc33-supply
additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
ap_ts: touchscreen@10 {
compatible = "elan,ekth6915";
reg = <0x10>;
interrupt-parent = <&tlmm>;
interrupts = <9 IRQ_TYPE_LEVEL_LOW>;
reset-gpios = <&tlmm 8 GPIO_ACTIVE_LOW>;
vcc33-supply = <&pp3300_ts>;
};
};

5
drivers/hid/.kunitconfig Normal file
View File

@ -0,0 +1,5 @@
CONFIG_KUNIT=y
CONFIG_USB=y
CONFIG_USB_HID=y
CONFIG_HID_UCLOGIC=y
CONFIG_HID_KUNIT_TEST=y

View File

@ -1306,6 +1306,22 @@ config HID_MCP2221
To compile this driver as a module, choose M here: the module To compile this driver as a module, choose M here: the module
will be called hid-mcp2221.ko. will be called hid-mcp2221.ko.
config HID_KUNIT_TEST
bool "KUnit tests for HID" if !KUNIT_ALL_TESTS
depends on KUNIT=y
depends on HID_UCLOGIC
default KUNIT_ALL_TESTS
help
This builds unit tests for HID. This option is not useful for
distributions or general kernels, but only for kernel
developers working on HID and associated drivers.
For more information on KUnit and unit tests in general,
please refer to the KUnit documentation in
Documentation/dev-tools/kunit/.
If in doubt, say "N".
endmenu endmenu
endif # HID endif # HID

View File

@ -144,6 +144,9 @@ obj-$(CONFIG_HID_WIIMOTE) += hid-wiimote.o
obj-$(CONFIG_HID_SENSOR_HUB) += hid-sensor-hub.o obj-$(CONFIG_HID_SENSOR_HUB) += hid-sensor-hub.o
obj-$(CONFIG_HID_SENSOR_CUSTOM_SENSOR) += hid-sensor-custom.o obj-$(CONFIG_HID_SENSOR_CUSTOM_SENSOR) += hid-sensor-custom.o
obj-$(CONFIG_HID_KUNIT_TEST) += hid-uclogic-rdesc.o \
hid-uclogic-rdesc-test.o
obj-$(CONFIG_USB_HID) += usbhid/ obj-$(CONFIG_USB_HID) += usbhid/
obj-$(CONFIG_USB_MOUSE) += usbhid/ obj-$(CONFIG_USB_MOUSE) += usbhid/
obj-$(CONFIG_USB_KBD) += usbhid/ obj-$(CONFIG_USB_KBD) += usbhid/

View File

@ -9,5 +9,8 @@ amd_sfh-objs := amd_sfh_hid.o
amd_sfh-objs += amd_sfh_client.o amd_sfh-objs += amd_sfh_client.o
amd_sfh-objs += amd_sfh_pcie.o amd_sfh-objs += amd_sfh_pcie.o
amd_sfh-objs += hid_descriptor/amd_sfh_hid_desc.o amd_sfh-objs += hid_descriptor/amd_sfh_hid_desc.o
amd_sfh-objs += sfh1_1/amd_sfh_init.o
amd_sfh-objs += sfh1_1/amd_sfh_interface.o
amd_sfh-objs += sfh1_1/amd_sfh_desc.o
ccflags-y += -I $(srctree)/$(src)/ ccflags-y += -I $(srctree)/$(src)/

View File

@ -18,18 +18,6 @@
#include "amd_sfh_pcie.h" #include "amd_sfh_pcie.h"
#include "amd_sfh_hid.h" #include "amd_sfh_hid.h"
struct request_list {
struct hid_device *hid;
struct list_head list;
u8 report_id;
u8 sensor_idx;
u8 report_type;
u8 current_index;
};
static struct request_list req_list;
void amd_sfh_set_report(struct hid_device *hid, int report_id, void amd_sfh_set_report(struct hid_device *hid, int report_id,
int report_type) int report_type)
{ {
@ -50,6 +38,7 @@ int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type)
{ {
struct amdtp_hid_data *hid_data = hid->driver_data; struct amdtp_hid_data *hid_data = hid->driver_data;
struct amdtp_cl_data *cli_data = hid_data->cli_data; struct amdtp_cl_data *cli_data = hid_data->cli_data;
struct request_list *req_list = &cli_data->req_list;
int i; int i;
for (i = 0; i < cli_data->num_hid_devices; i++) { for (i = 0; i < cli_data->num_hid_devices; i++) {
@ -66,7 +55,7 @@ int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type)
new->report_id = report_id; new->report_id = report_id;
cli_data->report_id[i] = report_id; cli_data->report_id[i] = report_id;
cli_data->request_done[i] = false; cli_data->request_done[i] = false;
list_add(&new->list, &req_list.list); list_add(&new->list, &req_list->list);
break; break;
} }
} }
@ -74,16 +63,19 @@ int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type)
return 0; return 0;
} }
static void amd_sfh_work(struct work_struct *work) void amd_sfh_work(struct work_struct *work)
{ {
struct amdtp_cl_data *cli_data = container_of(work, struct amdtp_cl_data, work.work); struct amdtp_cl_data *cli_data = container_of(work, struct amdtp_cl_data, work.work);
struct request_list *req_list = &cli_data->req_list;
struct amd_input_data *in_data = cli_data->in_data; struct amd_input_data *in_data = cli_data->in_data;
struct request_list *req_node; struct request_list *req_node;
u8 current_index, sensor_index; u8 current_index, sensor_index;
struct amd_mp2_ops *mp2_ops;
struct amd_mp2_dev *mp2;
u8 report_id, node_type; u8 report_id, node_type;
u8 report_size = 0; u8 report_size = 0;
req_node = list_last_entry(&req_list.list, struct request_list, list); req_node = list_last_entry(&req_list->list, struct request_list, list);
list_del(&req_node->list); list_del(&req_node->list);
current_index = req_node->current_index; current_index = req_node->current_index;
sensor_index = req_node->sensor_idx; sensor_index = req_node->sensor_idx;
@ -91,9 +83,11 @@ static void amd_sfh_work(struct work_struct *work)
node_type = req_node->report_type; node_type = req_node->report_type;
kfree(req_node); kfree(req_node);
mp2 = container_of(in_data, struct amd_mp2_dev, in_data);
mp2_ops = mp2->mp2_ops;
if (node_type == HID_FEATURE_REPORT) { if (node_type == HID_FEATURE_REPORT) {
report_size = get_feature_report(sensor_index, report_id, report_size = mp2_ops->get_feat_rep(sensor_index, report_id,
cli_data->feature_report[current_index]); cli_data->feature_report[current_index]);
if (report_size) if (report_size)
hid_input_report(cli_data->hid_sensor_hubs[current_index], hid_input_report(cli_data->hid_sensor_hubs[current_index],
cli_data->report_type[current_index], cli_data->report_type[current_index],
@ -102,7 +96,7 @@ static void amd_sfh_work(struct work_struct *work)
pr_err("AMDSFH: Invalid report size\n"); pr_err("AMDSFH: Invalid report size\n");
} else if (node_type == HID_INPUT_REPORT) { } else if (node_type == HID_INPUT_REPORT) {
report_size = get_input_report(current_index, sensor_index, report_id, in_data); report_size = mp2_ops->get_in_rep(current_index, sensor_index, report_id, in_data);
if (report_size) if (report_size)
hid_input_report(cli_data->hid_sensor_hubs[current_index], hid_input_report(cli_data->hid_sensor_hubs[current_index],
cli_data->report_type[current_index], cli_data->report_type[current_index],
@ -115,17 +109,19 @@ static void amd_sfh_work(struct work_struct *work)
amdtp_hid_wakeup(cli_data->hid_sensor_hubs[current_index]); amdtp_hid_wakeup(cli_data->hid_sensor_hubs[current_index]);
} }
static void amd_sfh_work_buffer(struct work_struct *work) void amd_sfh_work_buffer(struct work_struct *work)
{ {
struct amdtp_cl_data *cli_data = container_of(work, struct amdtp_cl_data, work_buffer.work); struct amdtp_cl_data *cli_data = container_of(work, struct amdtp_cl_data, work_buffer.work);
struct amd_input_data *in_data = cli_data->in_data; struct amd_input_data *in_data = cli_data->in_data;
struct amd_mp2_dev *mp2;
u8 report_size; u8 report_size;
int i; int i;
for (i = 0; i < cli_data->num_hid_devices; i++) { for (i = 0; i < cli_data->num_hid_devices; i++) {
if (cli_data->sensor_sts[i] == SENSOR_ENABLED) { if (cli_data->sensor_sts[i] == SENSOR_ENABLED) {
report_size = get_input_report mp2 = container_of(in_data, struct amd_mp2_dev, in_data);
(i, cli_data->sensor_idx[i], cli_data->report_id[i], in_data); report_size = mp2->mp2_ops->get_in_rep(i, cli_data->sensor_idx[i],
cli_data->report_id[i], in_data);
hid_input_report(cli_data->hid_sensor_hubs[i], HID_INPUT_REPORT, hid_input_report(cli_data->hid_sensor_hubs[i], HID_INPUT_REPORT,
in_data->input_report[i], report_size, 0); in_data->input_report[i], report_size, 0);
} }
@ -133,7 +129,7 @@ static void amd_sfh_work_buffer(struct work_struct *work)
schedule_delayed_work(&cli_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP)); schedule_delayed_work(&cli_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP));
} }
u32 amd_sfh_wait_for_response(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts) static u32 amd_sfh_wait_for_response(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts)
{ {
if (mp2->mp2_ops->response) if (mp2->mp2_ops->response)
sensor_sts = mp2->mp2_ops->response(mp2, sid, sensor_sts); sensor_sts = mp2->mp2_ops->response(mp2, sid, sensor_sts);
@ -141,7 +137,7 @@ u32 amd_sfh_wait_for_response(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts)
return sensor_sts; return sensor_sts;
} }
const char *get_sensor_name(int idx) static const char *get_sensor_name(int idx)
{ {
switch (idx) { switch (idx) {
case accel_idx: case accel_idx:
@ -159,24 +155,82 @@ const char *get_sensor_name(int idx)
} }
} }
static void amd_sfh_resume(struct amd_mp2_dev *mp2)
{
struct amdtp_cl_data *cl_data = mp2->cl_data;
struct amd_mp2_sensor_info info;
int i, status;
for (i = 0; i < cl_data->num_hid_devices; i++) {
if (cl_data->sensor_sts[i] == SENSOR_DISABLED) {
info.period = AMD_SFH_IDLE_LOOP;
info.sensor_idx = cl_data->sensor_idx[i];
info.dma_address = cl_data->sensor_dma_addr[i];
mp2->mp2_ops->start(mp2, info);
status = amd_sfh_wait_for_response
(mp2, cl_data->sensor_idx[i], SENSOR_ENABLED);
if (status == SENSOR_ENABLED)
cl_data->sensor_sts[i] = SENSOR_ENABLED;
dev_dbg(&mp2->pdev->dev, "resume sid 0x%x (%s) status 0x%x\n",
cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
cl_data->sensor_sts[i]);
}
}
schedule_delayed_work(&cl_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP));
amd_sfh_clear_intr(mp2);
}
static void amd_sfh_suspend(struct amd_mp2_dev *mp2)
{
struct amdtp_cl_data *cl_data = mp2->cl_data;
int i, status;
for (i = 0; i < cl_data->num_hid_devices; i++) {
if (cl_data->sensor_idx[i] != HPD_IDX &&
cl_data->sensor_sts[i] == SENSOR_ENABLED) {
mp2->mp2_ops->stop(mp2, cl_data->sensor_idx[i]);
status = amd_sfh_wait_for_response
(mp2, cl_data->sensor_idx[i], SENSOR_DISABLED);
if (status != SENSOR_ENABLED)
cl_data->sensor_sts[i] = SENSOR_DISABLED;
dev_dbg(&mp2->pdev->dev, "suspend sid 0x%x (%s) status 0x%x\n",
cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
cl_data->sensor_sts[i]);
}
}
cancel_delayed_work_sync(&cl_data->work_buffer);
amd_sfh_clear_intr(mp2);
}
int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata) int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
{ {
struct amd_input_data *in_data = &privdata->in_data; struct amd_input_data *in_data = &privdata->in_data;
struct amdtp_cl_data *cl_data = privdata->cl_data; struct amdtp_cl_data *cl_data = privdata->cl_data;
struct amd_mp2_ops *mp2_ops = privdata->mp2_ops;
struct amd_mp2_sensor_info info; struct amd_mp2_sensor_info info;
struct request_list *req_list;
struct device *dev; struct device *dev;
u32 feature_report_size; u32 feature_report_size;
u32 input_report_size; u32 input_report_size;
int rc, i, status; int rc, i, status;
u8 cl_idx; u8 cl_idx;
req_list = &cl_data->req_list;
dev = &privdata->pdev->dev; dev = &privdata->pdev->dev;
amd_sfh_set_desc_ops(mp2_ops);
mp2_ops->suspend = amd_sfh_suspend;
mp2_ops->resume = amd_sfh_resume;
cl_data->num_hid_devices = amd_mp2_get_sensor_num(privdata, &cl_data->sensor_idx[0]); cl_data->num_hid_devices = amd_mp2_get_sensor_num(privdata, &cl_data->sensor_idx[0]);
if (cl_data->num_hid_devices == 0)
return -ENODEV;
INIT_DELAYED_WORK(&cl_data->work, amd_sfh_work); INIT_DELAYED_WORK(&cl_data->work, amd_sfh_work);
INIT_DELAYED_WORK(&cl_data->work_buffer, amd_sfh_work_buffer); INIT_DELAYED_WORK(&cl_data->work_buffer, amd_sfh_work_buffer);
INIT_LIST_HEAD(&req_list.list); INIT_LIST_HEAD(&req_list->list);
cl_data->in_data = in_data; cl_data->in_data = in_data;
for (i = 0; i < cl_data->num_hid_devices; i++) { for (i = 0; i < cl_data->num_hid_devices; i++) {
@ -187,17 +241,17 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
cl_data->sensor_requested_cnt[i] = 0; cl_data->sensor_requested_cnt[i] = 0;
cl_data->cur_hid_dev = i; cl_data->cur_hid_dev = i;
cl_idx = cl_data->sensor_idx[i]; cl_idx = cl_data->sensor_idx[i];
cl_data->report_descr_sz[i] = get_descr_sz(cl_idx, descr_size); cl_data->report_descr_sz[i] = mp2_ops->get_desc_sz(cl_idx, descr_size);
if (!cl_data->report_descr_sz[i]) { if (!cl_data->report_descr_sz[i]) {
rc = -EINVAL; rc = -EINVAL;
goto cleanup; goto cleanup;
} }
feature_report_size = get_descr_sz(cl_idx, feature_size); feature_report_size = mp2_ops->get_desc_sz(cl_idx, feature_size);
if (!feature_report_size) { if (!feature_report_size) {
rc = -EINVAL; rc = -EINVAL;
goto cleanup; goto cleanup;
} }
input_report_size = get_descr_sz(cl_idx, input_size); input_report_size = mp2_ops->get_desc_sz(cl_idx, input_size);
if (!input_report_size) { if (!input_report_size) {
rc = -EINVAL; rc = -EINVAL;
goto cleanup; goto cleanup;
@ -222,17 +276,17 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
rc = -ENOMEM; rc = -ENOMEM;
goto cleanup; goto cleanup;
} }
rc = get_report_descriptor(cl_idx, cl_data->report_descr[i]); rc = mp2_ops->get_rep_desc(cl_idx, cl_data->report_descr[i]);
if (rc) if (rc)
return rc; return rc;
privdata->mp2_ops->start(privdata, info); mp2_ops->start(privdata, info);
status = amd_sfh_wait_for_response status = amd_sfh_wait_for_response
(privdata, cl_data->sensor_idx[i], SENSOR_ENABLED); (privdata, cl_data->sensor_idx[i], SENSOR_ENABLED);
if (status == SENSOR_ENABLED) { if (status == SENSOR_ENABLED) {
cl_data->sensor_sts[i] = SENSOR_ENABLED; cl_data->sensor_sts[i] = SENSOR_ENABLED;
rc = amdtp_hid_probe(cl_data->cur_hid_dev, cl_data); rc = amdtp_hid_probe(cl_data->cur_hid_dev, cl_data);
if (rc) { if (rc) {
privdata->mp2_ops->stop(privdata, cl_data->sensor_idx[i]); mp2_ops->stop(privdata, cl_data->sensor_idx[i]);
status = amd_sfh_wait_for_response status = amd_sfh_wait_for_response
(privdata, cl_data->sensor_idx[i], SENSOR_DISABLED); (privdata, cl_data->sensor_idx[i], SENSOR_DISABLED);
if (status != SENSOR_ENABLED) if (status != SENSOR_ENABLED)
@ -248,8 +302,7 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]), cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
cl_data->sensor_sts[i]); cl_data->sensor_sts[i]);
} }
if (privdata->mp2_ops->discovery_status && if (mp2_ops->discovery_status && mp2_ops->discovery_status(privdata) == 0) {
privdata->mp2_ops->discovery_status(privdata) == 0) {
amd_sfh_hid_client_deinit(privdata); amd_sfh_hid_client_deinit(privdata);
for (i = 0; i < cl_data->num_hid_devices; i++) { for (i = 0; i < cl_data->num_hid_devices; i++) {
devm_kfree(dev, cl_data->feature_report[i]); devm_kfree(dev, cl_data->feature_report[i]);

View File

@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* AMD MP2 common macros and structures
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
#ifndef AMD_SFH_COMMON_H
#define AMD_SFH_COMMON_H
#include <linux/pci.h>
#include "amd_sfh_hid.h"
#define PCI_DEVICE_ID_AMD_MP2 0x15E4
#define PCI_DEVICE_ID_AMD_MP2_1_1 0x164A
#define AMD_C2P_MSG(regno) (0x10500 + ((regno) * 4))
#define AMD_P2C_MSG(regno) (0x10680 + ((regno) * 4))
#define SENSOR_ENABLED 4
#define SENSOR_DISABLED 5
#define AMD_SFH_IDLE_LOOP 200
enum cmd_id {
NO_OP,
ENABLE_SENSOR,
DISABLE_SENSOR,
STOP_ALL_SENSORS = 8,
};
struct amd_mp2_sensor_info {
u8 sensor_idx;
u32 period;
dma_addr_t dma_address;
};
struct amd_mp2_dev {
struct pci_dev *pdev;
struct amdtp_cl_data *cl_data;
void __iomem *mmio;
void __iomem *vsbase;
const struct amd_sfh1_1_ops *sfh1_1_ops;
struct amd_mp2_ops *mp2_ops;
struct amd_input_data in_data;
/* mp2 active control status */
u32 mp2_acs;
};
struct amd_mp2_ops {
void (*start)(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info);
void (*stop)(struct amd_mp2_dev *privdata, u16 sensor_idx);
void (*stop_all)(struct amd_mp2_dev *privdata);
int (*response)(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts);
void (*clear_intr)(struct amd_mp2_dev *privdata);
int (*init_intr)(struct amd_mp2_dev *privdata);
int (*discovery_status)(struct amd_mp2_dev *privdata);
void (*suspend)(struct amd_mp2_dev *mp2);
void (*resume)(struct amd_mp2_dev *mp2);
void (*remove)(void *privdata);
int (*get_rep_desc)(int sensor_idx, u8 rep_desc[]);
u32 (*get_desc_sz)(int sensor_idx, int descriptor_name);
u8 (*get_feat_rep)(int sensor_idx, int report_id, u8 *feature_report);
u8 (*get_in_rep)(u8 current_index, int sensor_idx, int report_id,
struct amd_input_data *in_data);
};
void amd_sfh_work(struct work_struct *work);
void amd_sfh_work_buffer(struct work_struct *work);
void amd_sfh_clear_intr_v2(struct amd_mp2_dev *privdata);
int amd_sfh_irq_init_v2(struct amd_mp2_dev *privdata);
void amd_sfh_clear_intr(struct amd_mp2_dev *privdata);
int amd_sfh_irq_init(struct amd_mp2_dev *privdata);
#endif

View File

@ -101,11 +101,15 @@ static int amdtp_wait_for_response(struct hid_device *hid)
void amdtp_hid_wakeup(struct hid_device *hid) void amdtp_hid_wakeup(struct hid_device *hid)
{ {
struct amdtp_hid_data *hid_data = hid->driver_data; struct amdtp_hid_data *hid_data;
struct amdtp_cl_data *cli_data = hid_data->cli_data; struct amdtp_cl_data *cli_data;
cli_data->request_done[cli_data->cur_hid_dev] = true; if (hid) {
wake_up_interruptible(&hid_data->hid_wait); hid_data = hid->driver_data;
cli_data = hid_data->cli_data;
cli_data->request_done[cli_data->cur_hid_dev] = true;
wake_up_interruptible(&hid_data->hid_wait);
}
} }
static struct hid_ll_driver amdtp_hid_ll_driver = { static struct hid_ll_driver amdtp_hid_ll_driver = {

View File

@ -15,6 +15,15 @@
#define AMD_SFH_HID_VENDOR 0x1022 #define AMD_SFH_HID_VENDOR 0x1022
#define AMD_SFH_HID_PRODUCT 0x0001 #define AMD_SFH_HID_PRODUCT 0x0001
struct request_list {
struct hid_device *hid;
struct list_head list;
u8 report_id;
u8 sensor_idx;
u8 report_type;
u8 current_index;
};
struct amd_input_data { struct amd_input_data {
u32 *sensor_virt_addr[MAX_HID_DEVICES]; u32 *sensor_virt_addr[MAX_HID_DEVICES];
u8 *input_report[MAX_HID_DEVICES]; u8 *input_report[MAX_HID_DEVICES];
@ -43,6 +52,7 @@ struct amdtp_cl_data {
struct amd_input_data *in_data; struct amd_input_data *in_data;
struct delayed_work work; struct delayed_work work;
struct delayed_work work_buffer; struct delayed_work work_buffer;
struct request_list req_list;
}; };
/** /**
@ -69,6 +79,4 @@ void amdtp_hid_remove(struct amdtp_cl_data *cli_data);
int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type); int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type);
void amd_sfh_set_report(struct hid_device *hid, int report_id, int report_type); void amd_sfh_set_report(struct hid_device *hid, int report_id, int report_type);
void amdtp_hid_wakeup(struct hid_device *hid); void amdtp_hid_wakeup(struct hid_device *hid);
u8 get_input_report(u8 current_index, int sensor_idx, int report_id,
struct amd_input_data *in_data);
#endif #endif

View File

@ -19,6 +19,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include "amd_sfh_pcie.h" #include "amd_sfh_pcie.h"
#include "sfh1_1/amd_sfh_init.h"
#define DRIVER_NAME "pcie_mp2_amd" #define DRIVER_NAME "pcie_mp2_amd"
#define DRIVER_DESC "AMD(R) PCIe MP2 Communication Driver" #define DRIVER_DESC "AMD(R) PCIe MP2 Communication Driver"
@ -92,7 +93,7 @@ static void amd_stop_all_sensor_v2(struct amd_mp2_dev *privdata)
writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0); writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
} }
static void amd_sfh_clear_intr_v2(struct amd_mp2_dev *privdata) void amd_sfh_clear_intr_v2(struct amd_mp2_dev *privdata)
{ {
if (readl(privdata->mmio + AMD_P2C_MSG(4))) { if (readl(privdata->mmio + AMD_P2C_MSG(4))) {
writel(0, privdata->mmio + AMD_P2C_MSG(4)); writel(0, privdata->mmio + AMD_P2C_MSG(4));
@ -100,7 +101,7 @@ static void amd_sfh_clear_intr_v2(struct amd_mp2_dev *privdata)
} }
} }
static void amd_sfh_clear_intr(struct amd_mp2_dev *privdata) void amd_sfh_clear_intr(struct amd_mp2_dev *privdata)
{ {
if (privdata->mp2_ops->clear_intr) if (privdata->mp2_ops->clear_intr)
privdata->mp2_ops->clear_intr(privdata); privdata->mp2_ops->clear_intr(privdata);
@ -113,7 +114,7 @@ static irqreturn_t amd_sfh_irq_handler(int irq, void *data)
return IRQ_HANDLED; return IRQ_HANDLED;
} }
static int amd_sfh_irq_init_v2(struct amd_mp2_dev *privdata) int amd_sfh_irq_init_v2(struct amd_mp2_dev *privdata)
{ {
int rc; int rc;
@ -136,7 +137,7 @@ static int amd_sfh_dis_sts_v2(struct amd_mp2_dev *privdata)
SENSOR_DISCOVERY_STATUS_MASK) >> SENSOR_DISCOVERY_STATUS_SHIFT; SENSOR_DISCOVERY_STATUS_MASK) >> SENSOR_DISCOVERY_STATUS_SHIFT;
} }
void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info) static void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info)
{ {
union sfh_cmd_param cmd_param; union sfh_cmd_param cmd_param;
union sfh_cmd_base cmd_base; union sfh_cmd_base cmd_base;
@ -157,7 +158,7 @@ void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info i
writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0); writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
} }
void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx) static void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx)
{ {
union sfh_cmd_base cmd_base; union sfh_cmd_base cmd_base;
@ -171,7 +172,7 @@ void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx)
writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0); writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
} }
void amd_stop_all_sensors(struct amd_mp2_dev *privdata) static void amd_stop_all_sensors(struct amd_mp2_dev *privdata)
{ {
union sfh_cmd_base cmd_base; union sfh_cmd_base cmd_base;
@ -244,7 +245,7 @@ static void amd_mp2_pci_remove(void *privdata)
amd_sfh_clear_intr(mp2); amd_sfh_clear_intr(mp2);
} }
static const struct amd_mp2_ops amd_sfh_ops_v2 = { static struct amd_mp2_ops amd_sfh_ops_v2 = {
.start = amd_start_sensor_v2, .start = amd_start_sensor_v2,
.stop = amd_stop_sensor_v2, .stop = amd_stop_sensor_v2,
.stop_all = amd_stop_all_sensor_v2, .stop_all = amd_stop_all_sensor_v2,
@ -252,12 +253,14 @@ static const struct amd_mp2_ops amd_sfh_ops_v2 = {
.clear_intr = amd_sfh_clear_intr_v2, .clear_intr = amd_sfh_clear_intr_v2,
.init_intr = amd_sfh_irq_init_v2, .init_intr = amd_sfh_irq_init_v2,
.discovery_status = amd_sfh_dis_sts_v2, .discovery_status = amd_sfh_dis_sts_v2,
.remove = amd_mp2_pci_remove,
}; };
static const struct amd_mp2_ops amd_sfh_ops = { static struct amd_mp2_ops amd_sfh_ops = {
.start = amd_start_sensor, .start = amd_start_sensor,
.stop = amd_stop_sensor, .stop = amd_stop_sensor,
.stop_all = amd_stop_all_sensors, .stop_all = amd_stop_all_sensors,
.remove = amd_mp2_pci_remove,
}; };
static void mp2_select_ops(struct amd_mp2_dev *privdata) static void mp2_select_ops(struct amd_mp2_dev *privdata)
@ -277,7 +280,7 @@ static void mp2_select_ops(struct amd_mp2_dev *privdata)
} }
} }
static int amd_sfh_irq_init(struct amd_mp2_dev *privdata) int amd_sfh_irq_init(struct amd_mp2_dev *privdata)
{ {
if (privdata->mp2_ops->init_intr) if (privdata->mp2_ops->init_intr)
return privdata->mp2_ops->init_intr(privdata); return privdata->mp2_ops->init_intr(privdata);
@ -316,6 +319,14 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i
if (!privdata->cl_data) if (!privdata->cl_data)
return -ENOMEM; return -ENOMEM;
privdata->sfh1_1_ops = (const struct amd_sfh1_1_ops *)id->driver_data;
if (privdata->sfh1_1_ops) {
rc = privdata->sfh1_1_ops->init(privdata);
if (rc)
return rc;
goto init_done;
}
mp2_select_ops(privdata); mp2_select_ops(privdata);
rc = amd_sfh_irq_init(privdata); rc = amd_sfh_irq_init(privdata);
@ -327,40 +338,22 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i
rc = amd_sfh_hid_client_init(privdata); rc = amd_sfh_hid_client_init(privdata);
if (rc) { if (rc) {
amd_sfh_clear_intr(privdata); amd_sfh_clear_intr(privdata);
dev_err(&pdev->dev, "amd_sfh_hid_client_init failed\n"); if (rc != -EOPNOTSUPP)
dev_err(&pdev->dev, "amd_sfh_hid_client_init failed\n");
return rc; return rc;
} }
init_done:
amd_sfh_clear_intr(privdata); amd_sfh_clear_intr(privdata);
return devm_add_action_or_reset(&pdev->dev, amd_mp2_pci_remove, privdata); return devm_add_action_or_reset(&pdev->dev, privdata->mp2_ops->remove, privdata);
} }
static int __maybe_unused amd_mp2_pci_resume(struct device *dev) static int __maybe_unused amd_mp2_pci_resume(struct device *dev)
{ {
struct amd_mp2_dev *mp2 = dev_get_drvdata(dev); struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
struct amdtp_cl_data *cl_data = mp2->cl_data;
struct amd_mp2_sensor_info info;
int i, status;
for (i = 0; i < cl_data->num_hid_devices; i++) { mp2->mp2_ops->resume(mp2);
if (cl_data->sensor_sts[i] == SENSOR_DISABLED) {
info.period = AMD_SFH_IDLE_LOOP;
info.sensor_idx = cl_data->sensor_idx[i];
info.dma_address = cl_data->sensor_dma_addr[i];
mp2->mp2_ops->start(mp2, info);
status = amd_sfh_wait_for_response
(mp2, cl_data->sensor_idx[i], SENSOR_ENABLED);
if (status == SENSOR_ENABLED)
cl_data->sensor_sts[i] = SENSOR_ENABLED;
dev_dbg(dev, "suspend sid 0x%x (%s) status 0x%x\n",
cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
cl_data->sensor_sts[i]);
}
}
schedule_delayed_work(&cl_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP));
amd_sfh_clear_intr(mp2);
return 0; return 0;
} }
@ -368,25 +361,8 @@ static int __maybe_unused amd_mp2_pci_resume(struct device *dev)
static int __maybe_unused amd_mp2_pci_suspend(struct device *dev) static int __maybe_unused amd_mp2_pci_suspend(struct device *dev)
{ {
struct amd_mp2_dev *mp2 = dev_get_drvdata(dev); struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
struct amdtp_cl_data *cl_data = mp2->cl_data;
int i, status;
for (i = 0; i < cl_data->num_hid_devices; i++) { mp2->mp2_ops->suspend(mp2);
if (cl_data->sensor_idx[i] != HPD_IDX &&
cl_data->sensor_sts[i] == SENSOR_ENABLED) {
mp2->mp2_ops->stop(mp2, cl_data->sensor_idx[i]);
status = amd_sfh_wait_for_response
(mp2, cl_data->sensor_idx[i], SENSOR_DISABLED);
if (status != SENSOR_ENABLED)
cl_data->sensor_sts[i] = SENSOR_DISABLED;
dev_dbg(dev, "suspend sid 0x%x (%s) status 0x%x\n",
cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
cl_data->sensor_sts[i]);
}
}
cancel_delayed_work_sync(&cl_data->work_buffer);
amd_sfh_clear_intr(mp2);
return 0; return 0;
} }
@ -396,6 +372,8 @@ static SIMPLE_DEV_PM_OPS(amd_mp2_pm_ops, amd_mp2_pci_suspend,
static const struct pci_device_id amd_mp2_pci_tbl[] = { static const struct pci_device_id amd_mp2_pci_tbl[] = {
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2_1_1),
.driver_data = (kernel_ulong_t)&sfh1_1_ops },
{ } { }
}; };
MODULE_DEVICE_TABLE(pci, amd_mp2_pci_tbl); MODULE_DEVICE_TABLE(pci, amd_mp2_pci_tbl);

View File

@ -10,35 +10,20 @@
#ifndef PCIE_MP2_AMD_H #ifndef PCIE_MP2_AMD_H
#define PCIE_MP2_AMD_H #define PCIE_MP2_AMD_H
#include <linux/pci.h> #include "amd_sfh_common.h"
#include "amd_sfh_hid.h"
#define PCI_DEVICE_ID_AMD_MP2 0x15E4
#define ENABLE_SENSOR 1
#define DISABLE_SENSOR 2
#define STOP_ALL_SENSORS 8
/* MP2 C2P Message Registers */ /* MP2 C2P Message Registers */
#define AMD_C2P_MSG0 0x10500 #define AMD_C2P_MSG0 0x10500
#define AMD_C2P_MSG1 0x10504 #define AMD_C2P_MSG1 0x10504
#define AMD_C2P_MSG2 0x10508 #define AMD_C2P_MSG2 0x10508
#define AMD_C2P_MSG(regno) (0x10500 + ((regno) * 4))
#define AMD_P2C_MSG(regno) (0x10680 + ((regno) * 4))
/* MP2 P2C Message Registers */ /* MP2 P2C Message Registers */
#define AMD_P2C_MSG3 0x1068C /* Supported Sensors info */ #define AMD_P2C_MSG3 0x1068C /* Supported Sensors info */
#define V2_STATUS 0x2 #define V2_STATUS 0x2
#define SENSOR_ENABLED 4
#define SENSOR_DISABLED 5
#define HPD_IDX 16 #define HPD_IDX 16
#define AMD_SFH_IDLE_LOOP 200
#define SENSOR_DISCOVERY_STATUS_MASK GENMASK(5, 3) #define SENSOR_DISCOVERY_STATUS_MASK GENMASK(5, 3)
#define SENSOR_DISCOVERY_STATUS_SHIFT 3 #define SENSOR_DISCOVERY_STATUS_SHIFT 3
@ -96,22 +81,6 @@ enum sensor_idx {
als_idx = 19 als_idx = 19
}; };
struct amd_mp2_dev {
struct pci_dev *pdev;
struct amdtp_cl_data *cl_data;
void __iomem *mmio;
const struct amd_mp2_ops *mp2_ops;
struct amd_input_data in_data;
/* mp2 active control status */
u32 mp2_acs;
};
struct amd_mp2_sensor_info {
u8 sensor_idx;
u32 period;
dma_addr_t dma_address;
};
enum mem_use_type { enum mem_use_type {
USE_DRAM, USE_DRAM,
USE_C2P_REG, USE_C2P_REG,
@ -129,24 +98,9 @@ struct hpd_status {
}; };
}; };
void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info);
void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx);
void amd_stop_all_sensors(struct amd_mp2_dev *privdata);
int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id); int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id);
int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata); int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata);
int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata); int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata);
u32 amd_sfh_wait_for_response(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts); void amd_sfh_set_desc_ops(struct amd_mp2_ops *mp2_ops);
void amd_mp2_suspend(struct amd_mp2_dev *mp2);
void amd_mp2_resume(struct amd_mp2_dev *mp2);
const char *get_sensor_name(int idx);
struct amd_mp2_ops {
void (*start)(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info);
void (*stop)(struct amd_mp2_dev *privdata, u16 sensor_idx);
void (*stop_all)(struct amd_mp2_dev *privdata);
int (*response)(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts);
void (*clear_intr)(struct amd_mp2_dev *privdata);
int (*init_intr)(struct amd_mp2_dev *privdata);
int (*discovery_status)(struct amd_mp2_dev *privdata);
};
#endif #endif

View File

@ -29,7 +29,7 @@
#define HID_USAGE_SENSOR_EVENT_DATA_UPDATED_ENUM 0x04 #define HID_USAGE_SENSOR_EVENT_DATA_UPDATED_ENUM 0x04
#define ILLUMINANCE_MASK GENMASK(14, 0) #define ILLUMINANCE_MASK GENMASK(14, 0)
int get_report_descriptor(int sensor_idx, u8 *rep_desc) static int get_report_descriptor(int sensor_idx, u8 *rep_desc)
{ {
switch (sensor_idx) { switch (sensor_idx) {
case accel_idx: /* accel */ case accel_idx: /* accel */
@ -63,7 +63,7 @@ int get_report_descriptor(int sensor_idx, u8 *rep_desc)
return 0; return 0;
} }
u32 get_descr_sz(int sensor_idx, int descriptor_name) static u32 get_descr_sz(int sensor_idx, int descriptor_name)
{ {
switch (sensor_idx) { switch (sensor_idx) {
case accel_idx: case accel_idx:
@ -133,7 +133,7 @@ static void get_common_features(struct common_feature_property *common, int repo
common->report_interval = HID_DEFAULT_REPORT_INTERVAL; common->report_interval = HID_DEFAULT_REPORT_INTERVAL;
} }
u8 get_feature_report(int sensor_idx, int report_id, u8 *feature_report) static u8 get_feature_report(int sensor_idx, int report_id, u8 *feature_report)
{ {
struct accel3_feature_report acc_feature; struct accel3_feature_report acc_feature;
struct gyro_feature_report gyro_feature; struct gyro_feature_report gyro_feature;
@ -200,7 +200,8 @@ static void get_common_inputs(struct common_input_property *common, int report_i
common->event_type = HID_USAGE_SENSOR_EVENT_DATA_UPDATED_ENUM; common->event_type = HID_USAGE_SENSOR_EVENT_DATA_UPDATED_ENUM;
} }
u8 get_input_report(u8 current_index, int sensor_idx, int report_id, struct amd_input_data *in_data) static u8 get_input_report(u8 current_index, int sensor_idx, int report_id,
struct amd_input_data *in_data)
{ {
struct amd_mp2_dev *privdata = container_of(in_data, struct amd_mp2_dev, in_data); struct amd_mp2_dev *privdata = container_of(in_data, struct amd_mp2_dev, in_data);
u32 *sensor_virt_addr = in_data->sensor_virt_addr[current_index]; u32 *sensor_virt_addr = in_data->sensor_virt_addr[current_index];
@ -267,3 +268,11 @@ u8 get_input_report(u8 current_index, int sensor_idx, int report_id, struct amd_
} }
return report_size; return report_size;
} }
void amd_sfh_set_desc_ops(struct amd_mp2_ops *mp2_ops)
{
mp2_ops->get_rep_desc = get_report_descriptor;
mp2_ops->get_feat_rep = get_feature_report;
mp2_ops->get_in_rep = get_input_report;
mp2_ops->get_desc_sz = get_descr_sz;
}

View File

@ -111,7 +111,4 @@ struct hpd_input_report {
u8 human_presence; u8 human_presence;
} __packed; } __packed;
int get_report_descriptor(int sensor_idx, u8 rep_desc[]);
u32 get_descr_sz(int sensor_idx, int descriptor_name);
u8 get_feature_report(int sensor_idx, int report_id, u8 *feature_report);
#endif #endif

View File

@ -0,0 +1,300 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD MP2 1.1 descriptor interfaces
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
#include <linux/hid-sensor-ids.h>
#include "amd_sfh_interface.h"
#include "../hid_descriptor/amd_sfh_hid_desc.h"
#include "../hid_descriptor/amd_sfh_hid_report_desc.h"
#define SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM 0x41
#define SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM 0x51
#define HID_DEFAULT_REPORT_INTERVAL 0x50
#define HID_DEFAULT_MIN_VALUE 0X7F
#define HID_DEFAULT_MAX_VALUE 0x80
#define HID_DEFAULT_SENSITIVITY 0x7F
#define HID_USAGE_SENSOR_PROPERTY_CONNECTION_TYPE_PC_INTEGRATED_ENUM 0x01
/* state enums */
#define HID_USAGE_SENSOR_STATE_READY_ENUM 0x02
#define HID_USAGE_SENSOR_STATE_INITIALIZING_ENUM 0x05
#define HID_USAGE_SENSOR_EVENT_DATA_UPDATED_ENUM 0x04
static int get_report_desc(int sensor_idx, u8 *rep_desc)
{
switch (sensor_idx) {
case ACCEL_IDX: /* accelerometer */
memset(rep_desc, 0, sizeof(accel3_report_descriptor));
memcpy(rep_desc, accel3_report_descriptor,
sizeof(accel3_report_descriptor));
break;
case GYRO_IDX: /* gyroscope */
memset(rep_desc, 0, sizeof(gyro3_report_descriptor));
memcpy(rep_desc, gyro3_report_descriptor,
sizeof(gyro3_report_descriptor));
break;
case MAG_IDX: /* magnetometer */
memset(rep_desc, 0, sizeof(comp3_report_descriptor));
memcpy(rep_desc, comp3_report_descriptor,
sizeof(comp3_report_descriptor));
break;
case ALS_IDX: /* ambient light sensor */
memset(rep_desc, 0, sizeof(als_report_descriptor));
memcpy(rep_desc, als_report_descriptor,
sizeof(als_report_descriptor));
break;
case HPD_IDX: /* HPD sensor */
memset(rep_desc, 0, sizeof(hpd_report_descriptor));
memcpy(rep_desc, hpd_report_descriptor,
sizeof(hpd_report_descriptor));
break;
}
return 0;
}
static void get_common_features(struct common_feature_property *common, int report_id)
{
common->report_id = report_id;
common->connection_type = HID_USAGE_SENSOR_PROPERTY_CONNECTION_TYPE_PC_INTEGRATED_ENUM;
common->report_state = SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM;
common->power_state = SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM;
common->sensor_state = HID_USAGE_SENSOR_STATE_INITIALIZING_ENUM;
common->report_interval = HID_DEFAULT_REPORT_INTERVAL;
}
static u8 get_feature_rep(int sensor_idx, int report_id, u8 *feature_report)
{
struct magno_feature_report magno_feature;
struct accel3_feature_report acc_feature;
struct gyro_feature_report gyro_feature;
struct hpd_feature_report hpd_feature;
struct als_feature_report als_feature;
u8 report_size = 0;
if (!feature_report)
return report_size;
switch (sensor_idx) {
case ACCEL_IDX: /* accelerometer */
get_common_features(&acc_feature.common_property, report_id);
acc_feature.accel_change_sesnitivity = HID_DEFAULT_SENSITIVITY;
acc_feature.accel_sensitivity_min = HID_DEFAULT_MIN_VALUE;
acc_feature.accel_sensitivity_max = HID_DEFAULT_MAX_VALUE;
memcpy(feature_report, &acc_feature, sizeof(acc_feature));
report_size = sizeof(acc_feature);
break;
case GYRO_IDX: /* gyroscope */
get_common_features(&gyro_feature.common_property, report_id);
gyro_feature.gyro_change_sesnitivity = HID_DEFAULT_SENSITIVITY;
gyro_feature.gyro_sensitivity_min = HID_DEFAULT_MIN_VALUE;
gyro_feature.gyro_sensitivity_max = HID_DEFAULT_MAX_VALUE;
memcpy(feature_report, &gyro_feature, sizeof(gyro_feature));
report_size = sizeof(gyro_feature);
break;
case MAG_IDX: /* magnetometer */
get_common_features(&magno_feature.common_property, report_id);
magno_feature.magno_headingchange_sensitivity = HID_DEFAULT_SENSITIVITY;
magno_feature.heading_min = HID_DEFAULT_MIN_VALUE;
magno_feature.heading_max = HID_DEFAULT_MAX_VALUE;
magno_feature.flux_change_sensitivity = HID_DEFAULT_MIN_VALUE;
magno_feature.flux_min = HID_DEFAULT_MIN_VALUE;
magno_feature.flux_max = HID_DEFAULT_MAX_VALUE;
memcpy(feature_report, &magno_feature, sizeof(magno_feature));
report_size = sizeof(magno_feature);
break;
case ALS_IDX: /* ambient light sensor */
get_common_features(&als_feature.common_property, report_id);
als_feature.als_change_sesnitivity = HID_DEFAULT_SENSITIVITY;
als_feature.als_sensitivity_min = HID_DEFAULT_MIN_VALUE;
als_feature.als_sensitivity_max = HID_DEFAULT_MAX_VALUE;
memcpy(feature_report, &als_feature, sizeof(als_feature));
report_size = sizeof(als_feature);
break;
case HPD_IDX: /* human presence detection sensor */
get_common_features(&hpd_feature.common_property, report_id);
memcpy(feature_report, &hpd_feature, sizeof(hpd_feature));
report_size = sizeof(hpd_feature);
break;
}
return report_size;
}
static void get_common_inputs(struct common_input_property *common, int report_id)
{
common->report_id = report_id;
common->sensor_state = HID_USAGE_SENSOR_STATE_READY_ENUM;
common->event_type = HID_USAGE_SENSOR_EVENT_DATA_UPDATED_ENUM;
}
static int float_to_int(u32 float32)
{
int fraction, shift, mantissa, sign, exp, zeropre;
mantissa = float32 & GENMASK(22, 0);
sign = (float32 & BIT(31)) ? -1 : 1;
exp = (float32 & ~BIT(31)) >> 23;
if (!exp && !mantissa)
return 0;
exp -= 127;
if (exp < 0) {
exp = -exp;
zeropre = (((BIT(23) + mantissa) * 100) >> 23) >> exp;
return zeropre >= 50 ? sign : 0;
}
shift = 23 - exp;
float32 = BIT(exp) + (mantissa >> shift);
fraction = mantissa & GENMASK(shift - 1, 0);
return (((fraction * 100) >> shift) >= 50) ? sign * (float32 + 1) : sign * float32;
}
static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
struct amd_input_data *in_data)
{
struct amd_mp2_dev *mp2 = container_of(in_data, struct amd_mp2_dev, in_data);
u8 *input_report = in_data->input_report[current_index];
struct magno_input_report magno_input;
struct accel3_input_report acc_input;
struct gyro_input_report gyro_input;
struct als_input_report als_input;
struct hpd_input_report hpd_input;
struct sfh_accel_data accel_data;
struct sfh_gyro_data gyro_data;
struct sfh_mag_data mag_data;
struct sfh_als_data als_data;
struct hpd_status hpdstatus;
void __iomem *sensoraddr;
u8 report_size = 0;
if (!input_report)
return report_size;
switch (sensor_idx) {
case ACCEL_IDX: /* accelerometer */
sensoraddr = mp2->vsbase + (ACCEL_IDX * SENSOR_DATA_MEM_SIZE_DEFAULT) +
OFFSET_SENSOR_DATA_DEFAULT;
memcpy_fromio(&accel_data, sensoraddr, sizeof(struct sfh_accel_data));
get_common_inputs(&acc_input.common_property, report_id);
acc_input.in_accel_x_value = float_to_int(accel_data.acceldata.x) / 100;
acc_input.in_accel_y_value = float_to_int(accel_data.acceldata.y) / 100;
acc_input.in_accel_z_value = float_to_int(accel_data.acceldata.z) / 100;
memcpy(input_report, &acc_input, sizeof(acc_input));
report_size = sizeof(acc_input);
break;
case GYRO_IDX: /* gyroscope */
sensoraddr = mp2->vsbase + (GYRO_IDX * SENSOR_DATA_MEM_SIZE_DEFAULT) +
OFFSET_SENSOR_DATA_DEFAULT;
memcpy_fromio(&gyro_data, sensoraddr, sizeof(struct sfh_gyro_data));
get_common_inputs(&gyro_input.common_property, report_id);
gyro_input.in_angel_x_value = float_to_int(gyro_data.gyrodata.x) / 1000;
gyro_input.in_angel_y_value = float_to_int(gyro_data.gyrodata.y) / 1000;
gyro_input.in_angel_z_value = float_to_int(gyro_data.gyrodata.z) / 1000;
memcpy(input_report, &gyro_input, sizeof(gyro_input));
report_size = sizeof(gyro_input);
break;
case MAG_IDX: /* magnetometer */
sensoraddr = mp2->vsbase + (MAG_IDX * SENSOR_DATA_MEM_SIZE_DEFAULT) +
OFFSET_SENSOR_DATA_DEFAULT;
memcpy_fromio(&mag_data, sensoraddr, sizeof(struct sfh_mag_data));
get_common_inputs(&magno_input.common_property, report_id);
magno_input.in_magno_x = float_to_int(mag_data.magdata.x) / 100;
magno_input.in_magno_y = float_to_int(mag_data.magdata.y) / 100;
magno_input.in_magno_z = float_to_int(mag_data.magdata.z) / 100;
magno_input.in_magno_accuracy = mag_data.accuracy / 100;
memcpy(input_report, &magno_input, sizeof(magno_input));
report_size = sizeof(magno_input);
break;
case ALS_IDX:
sensoraddr = mp2->vsbase + (ALS_IDX * SENSOR_DATA_MEM_SIZE_DEFAULT) +
OFFSET_SENSOR_DATA_DEFAULT;
memcpy_fromio(&als_data, sensoraddr, sizeof(struct sfh_als_data));
get_common_inputs(&als_input.common_property, report_id);
als_input.illuminance_value = als_data.lux;
report_size = sizeof(als_input);
memcpy(input_report, &als_input, sizeof(als_input));
break;
case HPD_IDX:
get_common_inputs(&hpd_input.common_property, report_id);
hpdstatus.val = readl(mp2->mmio + AMD_C2P_MSG(4));
hpd_input.human_presence = hpdstatus.shpd.presence;
report_size = sizeof(hpd_input);
memcpy(input_report, &hpd_input, sizeof(hpd_input));
break;
}
return report_size;
}
static u32 get_desc_size(int sensor_idx, int descriptor_name)
{
switch (sensor_idx) {
case ACCEL_IDX:
switch (descriptor_name) {
case descr_size:
return sizeof(accel3_report_descriptor);
case input_size:
return sizeof(struct accel3_input_report);
case feature_size:
return sizeof(struct accel3_feature_report);
}
break;
case GYRO_IDX:
switch (descriptor_name) {
case descr_size:
return sizeof(gyro3_report_descriptor);
case input_size:
return sizeof(struct gyro_input_report);
case feature_size:
return sizeof(struct gyro_feature_report);
}
break;
case MAG_IDX:
switch (descriptor_name) {
case descr_size:
return sizeof(comp3_report_descriptor);
case input_size:
return sizeof(struct magno_input_report);
case feature_size:
return sizeof(struct magno_feature_report);
}
break;
case ALS_IDX:
switch (descriptor_name) {
case descr_size:
return sizeof(als_report_descriptor);
case input_size:
return sizeof(struct als_input_report);
case feature_size:
return sizeof(struct als_feature_report);
}
break;
case HPD_IDX:
switch (descriptor_name) {
case descr_size:
return sizeof(hpd_report_descriptor);
case input_size:
return sizeof(struct hpd_input_report);
case feature_size:
return sizeof(struct hpd_feature_report);
}
break;
}
return 0;
}
void amd_sfh1_1_set_desc_ops(struct amd_mp2_ops *mp2_ops)
{
mp2_ops->get_rep_desc = get_report_desc;
mp2_ops->get_feat_rep = get_feature_rep;
mp2_ops->get_desc_sz = get_desc_size;
mp2_ops->get_in_rep = get_input_rep;
}

View File

@ -0,0 +1,324 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD MP2 1.1 communication driver
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
#include <linux/delay.h>
#include <linux/hid.h>
#include "amd_sfh_init.h"
#include "amd_sfh_interface.h"
#include "../hid_descriptor/amd_sfh_hid_desc.h"
static int amd_sfh_get_sensor_num(struct amd_mp2_dev *mp2, u8 *sensor_id)
{
struct sfh_sensor_list *slist;
struct sfh_base_info binfo;
int num_of_sensors = 0;
int i;
memcpy_fromio(&binfo, mp2->vsbase, sizeof(struct sfh_base_info));
slist = &binfo.sbase.s_list;
for (i = 0; i < MAX_IDX; i++) {
switch (i) {
case ACCEL_IDX:
case GYRO_IDX:
case MAG_IDX:
case ALS_IDX:
case HPD_IDX:
if (BIT(i) & slist->sl.sensors)
sensor_id[num_of_sensors++] = i;
break;
}
}
return num_of_sensors;
}
static u32 amd_sfh_wait_for_response(struct amd_mp2_dev *mp2, u8 sid, u32 cmd_id)
{
if (mp2->mp2_ops->response)
return mp2->mp2_ops->response(mp2, sid, cmd_id);
return 0;
}
static const char *get_sensor_name(int idx)
{
switch (idx) {
case ACCEL_IDX:
return "accelerometer";
case GYRO_IDX:
return "gyroscope";
case MAG_IDX:
return "magnetometer";
case ALS_IDX:
return "ALS";
case HPD_IDX:
return "HPD";
default:
return "unknown sensor type";
}
}
static int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata)
{
struct amdtp_cl_data *cl_data = privdata->cl_data;
int i, status;
for (i = 0; i < cl_data->num_hid_devices; i++) {
if (cl_data->sensor_sts[i] == SENSOR_ENABLED) {
privdata->mp2_ops->stop(privdata, cl_data->sensor_idx[i]);
status = amd_sfh_wait_for_response
(privdata, cl_data->sensor_idx[i], DISABLE_SENSOR);
if (status == 0)
cl_data->sensor_sts[i] = SENSOR_DISABLED;
dev_dbg(&privdata->pdev->dev, "stopping sid 0x%x (%s) status 0x%x\n",
cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
cl_data->sensor_sts[i]);
}
}
cancel_delayed_work_sync(&cl_data->work);
cancel_delayed_work_sync(&cl_data->work_buffer);
amdtp_hid_remove(cl_data);
return 0;
}
static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
{
struct amd_input_data *in_data = &privdata->in_data;
struct amdtp_cl_data *cl_data = privdata->cl_data;
struct amd_mp2_ops *mp2_ops = privdata->mp2_ops;
struct amd_mp2_sensor_info info;
struct request_list *req_list;
u32 feature_report_size;
u32 input_report_size;
struct device *dev;
int rc, i, status;
u8 cl_idx;
req_list = &cl_data->req_list;
dev = &privdata->pdev->dev;
amd_sfh1_1_set_desc_ops(mp2_ops);
cl_data->num_hid_devices = amd_sfh_get_sensor_num(privdata, &cl_data->sensor_idx[0]);
INIT_DELAYED_WORK(&cl_data->work, amd_sfh_work);
INIT_DELAYED_WORK(&cl_data->work_buffer, amd_sfh_work_buffer);
INIT_LIST_HEAD(&req_list->list);
cl_data->in_data = in_data;
for (i = 0; i < cl_data->num_hid_devices; i++) {
cl_data->sensor_sts[i] = SENSOR_DISABLED;
cl_data->sensor_requested_cnt[i] = 0;
cl_data->cur_hid_dev = i;
cl_idx = cl_data->sensor_idx[i];
cl_data->report_descr_sz[i] = mp2_ops->get_desc_sz(cl_idx, descr_size);
if (!cl_data->report_descr_sz[i]) {
rc = -EINVAL;
goto cleanup;
}
feature_report_size = mp2_ops->get_desc_sz(cl_idx, feature_size);
if (!feature_report_size) {
rc = -EINVAL;
goto cleanup;
}
input_report_size = mp2_ops->get_desc_sz(cl_idx, input_size);
if (!input_report_size) {
rc = -EINVAL;
goto cleanup;
}
cl_data->feature_report[i] = devm_kzalloc(dev, feature_report_size, GFP_KERNEL);
if (!cl_data->feature_report[i]) {
rc = -ENOMEM;
goto cleanup;
}
in_data->input_report[i] = devm_kzalloc(dev, input_report_size, GFP_KERNEL);
if (!in_data->input_report[i]) {
rc = -ENOMEM;
goto cleanup;
}
info.sensor_idx = cl_idx;
cl_data->report_descr[i] =
devm_kzalloc(dev, cl_data->report_descr_sz[i], GFP_KERNEL);
if (!cl_data->report_descr[i]) {
rc = -ENOMEM;
goto cleanup;
}
rc = mp2_ops->get_rep_desc(cl_idx, cl_data->report_descr[i]);
if (rc)
return rc;
writel(0, privdata->mmio + AMD_P2C_MSG(0));
mp2_ops->start(privdata, info);
status = amd_sfh_wait_for_response
(privdata, cl_data->sensor_idx[i], ENABLE_SENSOR);
status = (status == 0) ? SENSOR_ENABLED : SENSOR_DISABLED;
if (status == SENSOR_ENABLED) {
cl_data->sensor_sts[i] = SENSOR_ENABLED;
rc = amdtp_hid_probe(i, cl_data);
if (rc) {
mp2_ops->stop(privdata, cl_data->sensor_idx[i]);
status = amd_sfh_wait_for_response
(privdata, cl_data->sensor_idx[i], DISABLE_SENSOR);
if (status == 0)
status = SENSOR_DISABLED;
if (status != SENSOR_ENABLED)
cl_data->sensor_sts[i] = SENSOR_DISABLED;
dev_dbg(dev, "sid 0x%x (%s) status 0x%x\n",
cl_data->sensor_idx[i],
get_sensor_name(cl_data->sensor_idx[i]),
cl_data->sensor_sts[i]);
goto cleanup;
}
}
dev_dbg(dev, "sid 0x%x (%s) status 0x%x\n",
cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
cl_data->sensor_sts[i]);
}
schedule_delayed_work(&cl_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP));
return 0;
cleanup:
amd_sfh_hid_client_deinit(privdata);
for (i = 0; i < cl_data->num_hid_devices; i++) {
devm_kfree(dev, cl_data->feature_report[i]);
devm_kfree(dev, in_data->input_report[i]);
devm_kfree(dev, cl_data->report_descr[i]);
}
return rc;
}
static void amd_sfh_resume(struct amd_mp2_dev *mp2)
{
struct amdtp_cl_data *cl_data = mp2->cl_data;
struct amd_mp2_sensor_info info;
int i, status;
for (i = 0; i < cl_data->num_hid_devices; i++) {
if (cl_data->sensor_sts[i] == SENSOR_DISABLED) {
info.sensor_idx = cl_data->sensor_idx[i];
mp2->mp2_ops->start(mp2, info);
status = amd_sfh_wait_for_response
(mp2, cl_data->sensor_idx[i], ENABLE_SENSOR);
if (status == 0)
status = SENSOR_ENABLED;
if (status == SENSOR_ENABLED)
cl_data->sensor_sts[i] = SENSOR_ENABLED;
dev_dbg(&mp2->pdev->dev, "resume sid 0x%x (%s) status 0x%x\n",
cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
cl_data->sensor_sts[i]);
}
}
schedule_delayed_work(&cl_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP));
amd_sfh_clear_intr(mp2);
}
static void amd_sfh_suspend(struct amd_mp2_dev *mp2)
{
struct amdtp_cl_data *cl_data = mp2->cl_data;
int i, status;
for (i = 0; i < cl_data->num_hid_devices; i++) {
if (cl_data->sensor_idx[i] != HPD_IDX &&
cl_data->sensor_sts[i] == SENSOR_ENABLED) {
mp2->mp2_ops->stop(mp2, cl_data->sensor_idx[i]);
status = amd_sfh_wait_for_response
(mp2, cl_data->sensor_idx[i], DISABLE_SENSOR);
if (status == 0)
status = SENSOR_DISABLED;
if (status != SENSOR_ENABLED)
cl_data->sensor_sts[i] = SENSOR_DISABLED;
dev_dbg(&mp2->pdev->dev, "suspend sid 0x%x (%s) status 0x%x\n",
cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
cl_data->sensor_sts[i]);
}
}
cancel_delayed_work_sync(&cl_data->work_buffer);
amd_sfh_clear_intr(mp2);
}
static void amd_mp2_pci_remove(void *privdata)
{
struct amd_mp2_dev *mp2 = privdata;
amd_sfh_hid_client_deinit(privdata);
mp2->mp2_ops->stop_all(mp2);
pci_intx(mp2->pdev, false);
amd_sfh_clear_intr(mp2);
}
static void amd_sfh_set_ops(struct amd_mp2_dev *mp2)
{
struct amd_mp2_ops *mp2_ops;
sfh_interface_init(mp2);
mp2_ops = mp2->mp2_ops;
mp2_ops->clear_intr = amd_sfh_clear_intr_v2,
mp2_ops->init_intr = amd_sfh_irq_init_v2,
mp2_ops->suspend = amd_sfh_suspend;
mp2_ops->resume = amd_sfh_resume;
mp2_ops->remove = amd_mp2_pci_remove;
}
int amd_sfh1_1_init(struct amd_mp2_dev *mp2)
{
u32 phy_base = readl(mp2->mmio + AMD_C2P_MSG(22));
struct device *dev = &mp2->pdev->dev;
struct sfh_base_info binfo;
int rc;
phy_base <<= 21;
if (!devm_request_mem_region(dev, phy_base, 128 * 1024, "amd_sfh")) {
dev_err(dev, "can't reserve mmio registers\n");
return -ENOMEM;
}
mp2->vsbase = devm_ioremap(dev, phy_base, 128 * 1024);
if (!mp2->vsbase) {
dev_err(dev, "failed to remap vsbase\n");
return -ENOMEM;
}
/* Before accessing give time for SFH firmware for processing configuration */
msleep(5000);
memcpy_fromio(&binfo, mp2->vsbase, sizeof(struct sfh_base_info));
if (binfo.sbase.fw_info.fw_ver == 0 || binfo.sbase.s_list.sl.sensors == 0) {
dev_err(dev, "failed to get sensors\n");
return -EOPNOTSUPP;
}
dev_dbg(dev, "firmware version 0x%x\n", binfo.sbase.fw_info.fw_ver);
amd_sfh_set_ops(mp2);
rc = amd_sfh_irq_init(mp2);
if (rc) {
dev_err(dev, "amd_sfh_irq_init failed\n");
return rc;
}
rc = amd_sfh1_1_hid_client_init(mp2);
if (rc) {
dev_err(dev, "amd_sfh1_1_hid_client_init failed\n");
return rc;
}
return rc;
}

View File

@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* AMD MP2 1.1 initialization structures
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
#ifndef AMD_SFH_INIT_H
#define AMD_SFH_INIT_H
#include "../amd_sfh_common.h"
struct amd_sfh1_1_ops {
int (*init)(struct amd_mp2_dev *mp2);
};
int amd_sfh1_1_init(struct amd_mp2_dev *mp2);
static const struct amd_sfh1_1_ops __maybe_unused sfh1_1_ops = {
.init = amd_sfh1_1_init,
};
#endif

View File

@ -0,0 +1,75 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD MP2 1.1 communication interfaces
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/iopoll.h>
#include "amd_sfh_interface.h"
static int amd_sfh_wait_response(struct amd_mp2_dev *mp2, u8 sid, u32 cmd_id)
{
struct sfh_cmd_response cmd_resp;
/* Get response with status within a max of 1600 ms timeout */
if (!readl_poll_timeout(mp2->mmio + AMD_P2C_MSG(0), cmd_resp.resp,
(cmd_resp.response.response == 0 &&
cmd_resp.response.cmd_id == cmd_id && (sid == 0xff ||
cmd_resp.response.sensor_id == sid)), 500, 1600000))
return cmd_resp.response.response;
return -1;
}
static void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info)
{
struct sfh_cmd_base cmd_base;
cmd_base.ul = 0;
cmd_base.cmd.cmd_id = ENABLE_SENSOR;
cmd_base.cmd.intr_disable = 0;
cmd_base.cmd.sensor_id = info.sensor_idx;
writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG(0));
}
static void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx)
{
struct sfh_cmd_base cmd_base;
cmd_base.ul = 0;
cmd_base.cmd.cmd_id = DISABLE_SENSOR;
cmd_base.cmd.intr_disable = 0;
cmd_base.cmd.sensor_id = sensor_idx;
writeq(0x0, privdata->mmio + AMD_C2P_MSG(1));
writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG(0));
}
static void amd_stop_all_sensor(struct amd_mp2_dev *privdata)
{
struct sfh_cmd_base cmd_base;
cmd_base.ul = 0;
cmd_base.cmd.cmd_id = STOP_ALL_SENSORS;
cmd_base.cmd.intr_disable = 0;
writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG(0));
}
static struct amd_mp2_ops amd_sfh_ops = {
.start = amd_start_sensor,
.stop = amd_stop_sensor,
.stop_all = amd_stop_all_sensor,
.response = amd_sfh_wait_response,
};
void sfh_interface_init(struct amd_mp2_dev *mp2)
{
mp2->mp2_ops = &amd_sfh_ops;
}

View File

@ -0,0 +1,154 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* AMD MP2 1.1 communication interfaces
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
#ifndef AMD_SFH_INTERFACE_H
#define AMD_SFH_INTERFACE_H
#include "../amd_sfh_common.h"
#define SENSOR_DATA_MEM_SIZE_DEFAULT 256
#define TOTAL_STATIC_MEM_DEFAULT 1024
#define OFFSET_SFH_INFO_BASE_DEFAULT 0
#define OFFSET_SENSOR_DATA_DEFAULT (OFFSET_SFH_INFO_BASE_DEFAULT + \
TOTAL_STATIC_MEM_DEFAULT)
enum sensor_index {
ACCEL_IDX,
GYRO_IDX,
MAG_IDX,
ALS_IDX = 4,
HPD_IDX = 5,
MAX_IDX = 15,
};
struct sfh_cmd_base {
union {
u32 ul;
struct {
u32 sensor_id : 4;
u32 cmd_id : 4;
u32 sub_cmd_id : 6;
u32 length : 12;
u32 rsvd : 5;
u32 intr_disable : 1;
} cmd;
};
};
struct sfh_cmd_response {
union {
u32 resp;
struct {
u32 response : 8;
u32 sensor_id : 4;
u32 cmd_id : 4;
u32 sub_cmd : 6;
u32 rsvd2 : 10;
} response;
};
};
struct sfh_platform_info {
union {
u32 pi;
struct {
u32 cust_id : 16;
u32 plat_id : 6;
u32 interface_id : 4;
u32 rsvd : 6;
} pinfo;
};
};
struct sfh_firmware_info {
union {
u32 fw_ver;
struct {
u32 minor_rev : 8;
u32 major_rev : 8;
u32 minor_ver : 8;
u32 major_ver : 8;
} fver;
};
};
struct sfh_sensor_list {
union {
u32 slist;
struct {
u32 sensors : 16;
u32 rsvd : 16;
} sl;
};
};
struct sfh_base_info {
union {
u32 sfh_base[24];
struct {
struct sfh_platform_info plat_info;
struct sfh_firmware_info fw_info;
struct sfh_sensor_list s_list;
} sbase;
};
};
struct sfh_common_data {
u64 timestamp;
u32 intr_cnt;
u32 featvalid : 16;
u32 rsvd : 13;
u32 sensor_state : 3;
};
struct sfh_float32 {
u32 x;
u32 y;
u32 z;
};
struct sfh_accel_data {
struct sfh_common_data commondata;
struct sfh_float32 acceldata;
u32 accelstatus;
};
struct sfh_gyro_data {
struct sfh_common_data commondata;
struct sfh_float32 gyrodata;
u32 result;
};
struct sfh_mag_data {
struct sfh_common_data commondata;
struct sfh_float32 magdata;
u32 accuracy;
};
struct sfh_als_data {
struct sfh_common_data commondata;
u16 lux;
};
struct hpd_status {
union {
struct {
u32 distance : 16;
u32 probablity : 8;
u32 presence : 2;
u32 rsvd : 5;
u32 state : 1;
} shpd;
u32 val;
};
};
void sfh_interface_init(struct amd_mp2_dev *mp2);
void amd_sfh1_1_set_desc_ops(struct amd_mp2_ops *mp2_ops);
#endif

View File

@ -830,6 +830,8 @@ static const struct hid_device_id alps_id[] = {
USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) }, USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) },
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY,
USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1) }, USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1) },
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY,
USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_UNICORN_LEGACY) },
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY,
USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_T4_BTNLESS) }, USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_T4_BTNLESS) },
{ } { }

View File

@ -36,7 +36,7 @@
#define APPLE_NUMLOCK_EMULATION BIT(8) #define APPLE_NUMLOCK_EMULATION BIT(8)
#define APPLE_RDESC_BATTERY BIT(9) #define APPLE_RDESC_BATTERY BIT(9)
#define APPLE_BACKLIGHT_CTL BIT(10) #define APPLE_BACKLIGHT_CTL BIT(10)
#define APPLE_IS_KEYCHRON BIT(11) #define APPLE_IS_NON_APPLE BIT(11)
#define APPLE_FLAG_FKEY 0x01 #define APPLE_FLAG_FKEY 0x01
@ -65,6 +65,10 @@ MODULE_PARM_DESC(swap_fn_leftctrl, "Swap the Fn and left Control keys. "
"(For people who want to keep PC keyboard muscle memory. " "(For people who want to keep PC keyboard muscle memory. "
"[0] = as-is, Mac layout, 1 = swapped, PC layout)"); "[0] = as-is, Mac layout, 1 = swapped, PC layout)");
struct apple_non_apple_keyboard {
char *name;
};
struct apple_sc_backlight { struct apple_sc_backlight {
struct led_classdev cdev; struct led_classdev cdev;
struct hid_device *hdev; struct hid_device *hdev;
@ -313,6 +317,27 @@ static const struct apple_key_translation swapped_fn_leftctrl_keys[] = {
{ } { }
}; };
static const struct apple_non_apple_keyboard non_apple_keyboards[] = {
{ "SONiX USB DEVICE" },
{ "Keychron" },
{ "AONE" },
{ "GANSS" }
};
static bool apple_is_non_apple_keyboard(struct hid_device *hdev)
{
int i;
for (i = 0; i < ARRAY_SIZE(non_apple_keyboards); i++) {
char *non_apple = non_apple_keyboards[i].name;
if (strncmp(hdev->name, non_apple, strlen(non_apple)) == 0)
return true;
}
return false;
}
static inline void apple_setup_key_translation(struct input_dev *input, static inline void apple_setup_key_translation(struct input_dev *input,
const struct apple_key_translation *table) const struct apple_key_translation *table)
{ {
@ -363,7 +388,7 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
} }
if (fnmode == 3) { if (fnmode == 3) {
real_fnmode = (asc->quirks & APPLE_IS_KEYCHRON) ? 2 : 1; real_fnmode = (asc->quirks & APPLE_IS_NON_APPLE) ? 2 : 1;
} else { } else {
real_fnmode = fnmode; real_fnmode = fnmode;
} }
@ -669,9 +694,9 @@ static int apple_input_configured(struct hid_device *hdev,
asc->quirks &= ~APPLE_HAS_FN; asc->quirks &= ~APPLE_HAS_FN;
} }
if (strncmp(hdev->name, "Keychron", 8) == 0) { if (apple_is_non_apple_keyboard(hdev)) {
hid_info(hdev, "Keychron keyboard detected; function keys will default to fnmode=2 behavior\n"); hid_info(hdev, "Non-apple keyboard detected; function keys will default to fnmode=2 behavior\n");
asc->quirks |= APPLE_IS_KEYCHRON; asc->quirks |= APPLE_IS_NON_APPLE;
} }
return 0; return 0;

View File

@ -1662,7 +1662,7 @@ static void hid_process_report(struct hid_device *hid,
/* first retrieve all incoming values in data */ /* first retrieve all incoming values in data */
for (a = 0; a < report->maxfield; a++) for (a = 0; a < report->maxfield; a++)
hid_input_fetch_field(hid, field = report->field[a], data); hid_input_fetch_field(hid, report->field[a], data);
if (!list_empty(&report->field_entry_list)) { if (!list_empty(&report->field_entry_list)) {
/* INPUT_REPORT, we have a priority list of fields */ /* INPUT_REPORT, we have a priority list of fields */

View File

@ -790,6 +790,11 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
data->word = le16_to_cpup((__le16 *)buf); data->word = le16_to_cpup((__le16 *)buf);
break; break;
case I2C_SMBUS_I2C_BLOCK_DATA: case I2C_SMBUS_I2C_BLOCK_DATA:
if (read_length > I2C_SMBUS_BLOCK_MAX) {
ret = -EINVAL;
goto power_normal;
}
memcpy(data->block + 1, buf, read_length); memcpy(data->block + 1, buf, read_length);
break; break;
case I2C_SMBUS_BLOCK_DATA: case I2C_SMBUS_BLOCK_DATA:

View File

@ -413,6 +413,7 @@
#define USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN 0x2544 #define USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN 0x2544
#define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706 #define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706
#define I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN 0x261A #define I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN 0x261A
#define I2C_DEVICE_ID_SURFACE_GO2_TOUCHSCREEN 0x2A1C
#define USB_VENDOR_ID_ELECOM 0x056e #define USB_VENDOR_ID_ELECOM 0x056e
#define USB_DEVICE_ID_ELECOM_BM084 0x0061 #define USB_DEVICE_ID_ELECOM_BM084 0x0061
@ -1278,6 +1279,7 @@
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540 0x0075 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540 0x0075
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640 0x0094 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640 0x0094
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01 0x0042 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01 0x0042
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L 0x0935
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078
#define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074 #define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074
#define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071 #define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071

View File

@ -381,6 +381,8 @@ static const struct hid_device_id hid_battery_quirks[] = {
HID_BATTERY_QUIRK_IGNORE }, HID_BATTERY_QUIRK_IGNORE },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN), { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN),
HID_BATTERY_QUIRK_IGNORE }, HID_BATTERY_QUIRK_IGNORE },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO2_TOUCHSCREEN),
HID_BATTERY_QUIRK_IGNORE },
{} {}
}; };

View File

@ -766,7 +766,7 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
/* /*
* Some models have multiple interfaces, we want the interface with * Some models have multiple interfaces, we want the interface with
* with the f000.0000 application input report. * the f000.0000 application input report.
*/ */
rep_enum = &hdev->report_enum[HID_INPUT_REPORT]; rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
list_for_each_entry(rep, &rep_enum->report_list, list) { list_for_each_entry(rep, &rep_enum->report_list, list) {

View File

@ -1694,7 +1694,7 @@ static int hidpp_battery_get_property(struct power_supply *psy,
val->strval = hidpp->hid_dev->uniq; val->strval = hidpp->hid_dev->uniq;
break; break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW: case POWER_SUPPLY_PROP_VOLTAGE_NOW:
/* hardware reports voltage in in mV. sysfs expects uV */ /* hardware reports voltage in mV. sysfs expects uV */
val->intval = hidpp->battery.voltage * 1000; val->intval = hidpp->battery.voltage * 1000;
break; break;
case POWER_SUPPLY_PROP_CHARGE_TYPE: case POWER_SUPPLY_PROP_CHARGE_TYPE:

View File

@ -385,6 +385,9 @@ static int mcp_smbus_write(struct mcp2221 *mcp, u16 addr,
data_len = 7; data_len = 7;
break; break;
default: default:
if (len > I2C_SMBUS_BLOCK_MAX)
return -EINVAL;
memcpy(&mcp->txbuf[5], buf, len); memcpy(&mcp->txbuf[5], buf, len);
data_len = len + 5; data_len = len + 5;
} }

View File

@ -194,6 +194,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app);
#define MT_CLS_WIN_8_FORCE_MULTI_INPUT 0x0015 #define MT_CLS_WIN_8_FORCE_MULTI_INPUT 0x0015
#define MT_CLS_WIN_8_DISABLE_WAKEUP 0x0016 #define MT_CLS_WIN_8_DISABLE_WAKEUP 0x0016
#define MT_CLS_WIN_8_NO_STICKY_FINGERS 0x0017 #define MT_CLS_WIN_8_NO_STICKY_FINGERS 0x0017
#define MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU 0x0018
/* vendor specific classes */ /* vendor specific classes */
#define MT_CLS_3M 0x0101 #define MT_CLS_3M 0x0101
@ -286,6 +287,15 @@ static const struct mt_class mt_classes[] = {
MT_QUIRK_WIN8_PTP_BUTTONS | MT_QUIRK_WIN8_PTP_BUTTONS |
MT_QUIRK_FORCE_MULTI_INPUT, MT_QUIRK_FORCE_MULTI_INPUT,
.export_all_inputs = true }, .export_all_inputs = true },
{ .name = MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU,
.quirks = MT_QUIRK_IGNORE_DUPLICATES |
MT_QUIRK_HOVERING |
MT_QUIRK_CONTACT_CNT_ACCURATE |
MT_QUIRK_STICKY_FINGERS |
MT_QUIRK_WIN8_PTP_BUTTONS |
MT_QUIRK_FORCE_MULTI_INPUT |
MT_QUIRK_NOT_SEEN_MEANS_UP,
.export_all_inputs = true },
{ .name = MT_CLS_WIN_8_DISABLE_WAKEUP, { .name = MT_CLS_WIN_8_DISABLE_WAKEUP,
.quirks = MT_QUIRK_ALWAYS_VALID | .quirks = MT_QUIRK_ALWAYS_VALID |
MT_QUIRK_IGNORE_DUPLICATES | MT_QUIRK_IGNORE_DUPLICATES |
@ -783,6 +793,7 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
case HID_DG_CONFIDENCE: case HID_DG_CONFIDENCE:
if ((cls->name == MT_CLS_WIN_8 || if ((cls->name == MT_CLS_WIN_8 ||
cls->name == MT_CLS_WIN_8_FORCE_MULTI_INPUT || cls->name == MT_CLS_WIN_8_FORCE_MULTI_INPUT ||
cls->name == MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU ||
cls->name == MT_CLS_WIN_8_DISABLE_WAKEUP) && cls->name == MT_CLS_WIN_8_DISABLE_WAKEUP) &&
(field->application == HID_DG_TOUCHPAD || (field->application == HID_DG_TOUCHPAD ||
field->application == HID_DG_TOUCHSCREEN)) field->application == HID_DG_TOUCHSCREEN))
@ -2035,7 +2046,7 @@ static const struct hid_device_id mt_devices[] = {
USB_DEVICE_ID_LENOVO_X1_TAB3) }, USB_DEVICE_ID_LENOVO_X1_TAB3) },
/* Lenovo X12 TAB Gen 1 */ /* Lenovo X12 TAB Gen 1 */
{ .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT, { .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU,
HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8,
USB_VENDOR_ID_LENOVO, USB_VENDOR_ID_LENOVO,
USB_DEVICE_ID_LENOVO_X12_TAB) }, USB_DEVICE_ID_LENOVO_X12_TAB) },

View File

@ -292,6 +292,7 @@ static const struct joycon_rumble_amp_data joycon_rumble_amplitudes[] = {
}; };
static const u16 JC_RUMBLE_DFLT_LOW_FREQ = 160; static const u16 JC_RUMBLE_DFLT_LOW_FREQ = 160;
static const u16 JC_RUMBLE_DFLT_HIGH_FREQ = 320; static const u16 JC_RUMBLE_DFLT_HIGH_FREQ = 320;
static const unsigned short JC_RUMBLE_ZERO_AMP_PKT_CNT = 5;
#endif /* IS_ENABLED(CONFIG_NINTENDO_FF) */ #endif /* IS_ENABLED(CONFIG_NINTENDO_FF) */
static const u16 JC_RUMBLE_PERIOD_MS = 50; static const u16 JC_RUMBLE_PERIOD_MS = 50;
@ -402,8 +403,6 @@ struct joycon_input_report {
#define JC_RUMBLE_DATA_SIZE 8 #define JC_RUMBLE_DATA_SIZE 8
#define JC_RUMBLE_QUEUE_SIZE 8 #define JC_RUMBLE_QUEUE_SIZE 8
static const unsigned short JC_RUMBLE_ZERO_AMP_PKT_CNT = 5;
static const char * const joycon_player_led_names[] = { static const char * const joycon_player_led_names[] = {
LED_FUNCTION_PLAYER1, LED_FUNCTION_PLAYER1,
LED_FUNCTION_PLAYER2, LED_FUNCTION_PLAYER2,
@ -1586,6 +1585,7 @@ static const unsigned int joycon_button_inputs_r[] = {
/* We report joy-con d-pad inputs as buttons and pro controller as a hat. */ /* We report joy-con d-pad inputs as buttons and pro controller as a hat. */
static const unsigned int joycon_dpad_inputs_jc[] = { static const unsigned int joycon_dpad_inputs_jc[] = {
BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT, BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT,
0 /* 0 signals end of array */
}; };
static int joycon_input_create(struct joycon_ctlr *ctlr) static int joycon_input_create(struct joycon_ctlr *ctlr)
@ -1634,6 +1634,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
ctlr->input->id.version = hdev->version; ctlr->input->id.version = hdev->version;
ctlr->input->uniq = ctlr->mac_addr_str; ctlr->input->uniq = ctlr->mac_addr_str;
ctlr->input->name = name; ctlr->input->name = name;
ctlr->input->phys = hdev->phys;
input_set_drvdata(ctlr->input, ctlr); input_set_drvdata(ctlr->input, ctlr);
/* set up sticks and buttons */ /* set up sticks and buttons */
@ -1713,6 +1714,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
ctlr->imu_input->id.version = hdev->version; ctlr->imu_input->id.version = hdev->version;
ctlr->imu_input->uniq = ctlr->mac_addr_str; ctlr->imu_input->uniq = ctlr->mac_addr_str;
ctlr->imu_input->name = imu_name; ctlr->imu_input->name = imu_name;
ctlr->imu_input->phys = hdev->phys;
input_set_drvdata(ctlr->imu_input, ctlr); input_set_drvdata(ctlr->imu_input, ctlr);
/* configure imu axes */ /* configure imu axes */

View File

@ -521,6 +521,8 @@ static const struct hid_device_id uclogic_devices[] = {
USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640) }, USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01) }, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06) }, USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06) },
{ } { }

View File

@ -23,11 +23,11 @@
/** /**
* uclogic_params_pen_inrange_to_str() - Convert a pen in-range reporting type * uclogic_params_pen_inrange_to_str() - Convert a pen in-range reporting type
* to a string. * to a string.
*
* @inrange: The in-range reporting type to convert. * @inrange: The in-range reporting type to convert.
* *
* Returns: * Return:
* The string representing the type, or NULL if the type is unknown. * * The string representing the type, or
* * %NULL if the type is unknown.
*/ */
static const char *uclogic_params_pen_inrange_to_str( static const char *uclogic_params_pen_inrange_to_str(
enum uclogic_params_pen_inrange inrange) enum uclogic_params_pen_inrange inrange)
@ -45,10 +45,12 @@ static const char *uclogic_params_pen_inrange_to_str(
} }
/** /**
* Dump tablet interface pen parameters with hid_dbg(), indented with one tab. * uclogic_params_pen_hid_dbg() - Dump tablet interface pen parameters
*
* @hdev: The HID device the pen parameters describe. * @hdev: The HID device the pen parameters describe.
* @pen: The pen parameters to dump. * @pen: The pen parameters to dump.
*
* Dump tablet interface pen parameters with hid_dbg(). The dump is indented
* with a tab.
*/ */
static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev, static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev,
const struct uclogic_params_pen *pen) const struct uclogic_params_pen *pen)
@ -77,11 +79,12 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev,
} }
/** /**
* Dump tablet interface frame parameters with hid_dbg(), indented with two * uclogic_params_frame_hid_dbg() - Dump tablet interface frame parameters
* tabs.
*
* @hdev: The HID device the pen parameters describe. * @hdev: The HID device the pen parameters describe.
* @frame: The frame parameters to dump. * @frame: The frame parameters to dump.
*
* Dump tablet interface frame parameters with hid_dbg(). The dump is
* indented with two tabs.
*/ */
static void uclogic_params_frame_hid_dbg( static void uclogic_params_frame_hid_dbg(
const struct hid_device *hdev, const struct hid_device *hdev,
@ -102,10 +105,11 @@ static void uclogic_params_frame_hid_dbg(
} }
/** /**
* Dump tablet interface parameters with hid_dbg(). * uclogic_params_hid_dbg() - Dump tablet interface parameters
*
* @hdev: The HID device the parameters describe. * @hdev: The HID device the parameters describe.
* @params: The parameters to dump. * @params: The parameters to dump.
*
* Dump tablet interface parameters with hid_dbg().
*/ */
void uclogic_params_hid_dbg(const struct hid_device *hdev, void uclogic_params_hid_dbg(const struct hid_device *hdev,
const struct uclogic_params *params) const struct uclogic_params *params)
@ -234,7 +238,7 @@ static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
const int len = 12; const int len = 12;
s32 resolution; s32 resolution;
/* Pen report descriptor template parameters */ /* Pen report descriptor template parameters */
s32 desc_params[UCLOGIC_RDESC_PEN_PH_ID_NUM]; s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
__u8 *desc_ptr = NULL; __u8 *desc_ptr = NULL;
/* Check arguments */ /* Check arguments */
@ -379,7 +383,7 @@ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen,
size_t i; size_t i;
s32 resolution; s32 resolution;
/* Pen report descriptor template parameters */ /* Pen report descriptor template parameters */
s32 desc_params[UCLOGIC_RDESC_PEN_PH_ID_NUM]; s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
__u8 *desc_ptr = NULL; __u8 *desc_ptr = NULL;
/* Check arguments */ /* Check arguments */
@ -1002,6 +1006,197 @@ cleanup:
return rc; return rc;
} }
/**
* uclogic_probe_interface() - some tablets, like the Parblo A610 PLUS V2 or
* the XP-PEN Deco Mini 7, need to be initialized by sending them magic data.
*
* @hdev: The HID device of the tablet interface to initialize and get
* parameters from. Cannot be NULL.
* @magic_arr: The magic data that should be sent to probe the interface.
* Cannot be NULL.
* @magic_size: Size of the magic data.
* @endpoint: Endpoint where the magic data should be sent.
*
* Returns:
* Zero, if successful. A negative errno code on error.
*/
static int uclogic_probe_interface(struct hid_device *hdev, u8 *magic_arr,
int magic_size, int endpoint)
{
struct usb_device *udev;
unsigned int pipe = 0;
int sent;
u8 *buf = NULL;
int rc = 0;
if (!hdev || !magic_arr) {
rc = -EINVAL;
goto cleanup;
}
buf = kmemdup(magic_arr, magic_size, GFP_KERNEL);
if (!buf) {
rc = -ENOMEM;
goto cleanup;
}
udev = hid_to_usb_dev(hdev);
pipe = usb_sndintpipe(udev, endpoint);
rc = usb_interrupt_msg(udev, pipe, buf, magic_size, &sent, 1000);
if (rc || sent != magic_size) {
hid_err(hdev, "Interface probing failed: %d\n", rc);
rc = -1;
goto cleanup;
}
rc = 0;
cleanup:
kfree(buf);
return rc;
}
/**
* uclogic_params_ugee_v2_init() - initialize a UGEE graphics tablets by
* discovering their parameters.
*
* These tables, internally designed as v2 to differentiate them from older
* models, expect a payload of magic data in orther to be switched to the fully
* functional mode and expose their parameters in a similar way to the
* information present in uclogic_params_pen_init_v1() but with some
* differences.
*
* @params: Parameters to fill in (to be cleaned with
* uclogic_params_cleanup()). Not modified in case of error.
* Cannot be NULL.
* @hdev: The HID device of the tablet interface to initialize and get
* parameters from. Cannot be NULL.
*
* Returns:
* Zero, if successful. A negative errno code on error.
*/
static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
struct hid_device *hdev)
{
int rc = 0;
struct usb_interface *iface;
__u8 bInterfaceNumber;
const int str_desc_len = 12;
__u8 *str_desc = NULL;
__u8 *rdesc_pen = NULL;
__u8 *rdesc_frame = NULL;
s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
s32 resolution;
__u8 magic_arr[] = {
0x02, 0xb0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
/* The resulting parameters (noop) */
struct uclogic_params p = {0, };
if (!params || !hdev) {
rc = -EINVAL;
goto cleanup;
}
iface = to_usb_interface(hdev->dev.parent);
bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
if (bInterfaceNumber != 2) {
uclogic_params_init_invalid(&p);
goto output;
}
/*
* Initialize the interface by sending magic data.
* The specific data was discovered by sniffing the Windows driver
* traffic.
*/
rc = uclogic_probe_interface(hdev, magic_arr, sizeof(magic_arr), 0x03);
if (rc) {
uclogic_params_init_invalid(&p);
goto output;
}
/*
* Read the string descriptor containing pen and frame parameters.
* The specific string descriptor and data were discovered by sniffing
* the Windows driver traffic.
*/
rc = uclogic_params_get_str_desc(&str_desc, hdev, 100, str_desc_len);
if (rc != str_desc_len) {
hid_err(hdev, "failed retrieving pen and frame parameters: %d\n", rc);
uclogic_params_init_invalid(&p);
goto output;
}
desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] =
get_unaligned_le16(str_desc + 2);
desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] =
get_unaligned_le16(str_desc + 4);
desc_params[UCLOGIC_RDESC_FRAME_PH_ID_UM] = str_desc[6];
desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] =
get_unaligned_le16(str_desc + 8);
resolution = get_unaligned_le16(str_desc + 10);
if (resolution == 0) {
desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0;
desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0;
} else {
desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] =
desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * 1000 /
resolution;
desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] =
desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * 1000 /
resolution;
}
kfree(str_desc);
str_desc = NULL;
/* Initialize the pen interface */
rdesc_pen = uclogic_rdesc_template_apply(
uclogic_rdesc_ugee_v2_pen_template_arr,
uclogic_rdesc_ugee_v2_pen_template_size,
desc_params, ARRAY_SIZE(desc_params));
if (!rdesc_pen) {
rc = -ENOMEM;
goto cleanup;
}
p.pen.desc_ptr = rdesc_pen;
p.pen.desc_size = uclogic_rdesc_ugee_v2_pen_template_size;
p.pen.id = 0x02;
p.pen.subreport_list[0].value = 0xf0;
p.pen.subreport_list[0].id = UCLOGIC_RDESC_V1_FRAME_ID;
/* Initialize the frame interface */
rdesc_frame = uclogic_rdesc_template_apply(
uclogic_rdesc_ugee_v2_frame_btn_template_arr,
uclogic_rdesc_ugee_v2_frame_btn_template_size,
desc_params, ARRAY_SIZE(desc_params));
if (!rdesc_frame) {
rc = -ENOMEM;
goto cleanup;
}
rc = uclogic_params_frame_init_with_desc(&p.frame_list[0],
rdesc_frame,
uclogic_rdesc_ugee_v2_frame_btn_template_size,
UCLOGIC_RDESC_V1_FRAME_ID);
kfree(rdesc_frame);
if (rc) {
uclogic_params_init_invalid(&p);
goto output;
}
output:
/* Output parameters */
memcpy(params, &p, sizeof(*params));
memset(&p, 0, sizeof(p));
rc = 0;
cleanup:
kfree(str_desc);
uclogic_params_cleanup(&p);
return rc;
}
/** /**
* uclogic_params_init() - initialize a tablet interface and discover its * uclogic_params_init() - initialize a tablet interface and discover its
* parameters. * parameters.
@ -1237,6 +1432,12 @@ int uclogic_params_init(struct uclogic_params *params,
uclogic_params_init_invalid(&p); uclogic_params_init_invalid(&p);
} }
break; break;
case VID_PID(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L):
rc = uclogic_params_ugee_v2_init(&p, hdev);
if (rc != 0)
goto cleanup;
break;
case VID_PID(USB_VENDOR_ID_TRUST, case VID_PID(USB_VENDOR_ID_TRUST,
USB_DEVICE_ID_TRUST_PANORA_TABLET): USB_DEVICE_ID_TRUST_PANORA_TABLET):
case VID_PID(USB_VENDOR_ID_UGEE, case VID_PID(USB_VENDOR_ID_UGEE,

View File

@ -0,0 +1,219 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* HID driver for UC-Logic devices not fully compliant with HID standard
*
* Copyright (c) 2022 José Expósito <jose.exposito89@gmail.com>
*/
#include <kunit/test.h>
#include "./hid-uclogic-rdesc.h"
struct uclogic_template_case {
const char *name;
const __u8 *template;
size_t template_size;
const s32 *param_list;
size_t param_num;
const __u8 *expected;
};
static const s32 params_pen_all[UCLOGIC_RDESC_PH_ID_NUM] = {
[UCLOGIC_RDESC_PEN_PH_ID_X_LM] = 0xAA,
[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0xBB,
[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] = 0xCC,
[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0xDD,
[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] = 0xEE,
};
static const s32 params_pen_some[] = {
[UCLOGIC_RDESC_PEN_PH_ID_X_LM] = 0xAA,
[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0xBB,
};
static const s32 params_frame_all[UCLOGIC_RDESC_PH_ID_NUM] = {
[UCLOGIC_RDESC_FRAME_PH_ID_UM] = 0xFF,
};
static const __u8 template_empty[] = { };
static const __u8 template_small[] = { 0x00 };
static const __u8 template_no_ph[] = { 0xAA, 0xFE, 0xAA, 0xED, 0x1D };
static const __u8 template_pen_ph_end[] = {
0xAA, 0xBB, UCLOGIC_RDESC_PEN_PH_HEAD
};
static const __u8 template_btn_ph_end[] = {
0xAA, 0xBB, UCLOGIC_RDESC_FRAME_PH_BTN_HEAD
};
static const __u8 template_pen_all_params[] = {
UCLOGIC_RDESC_PEN_PH(X_LM),
0x47, UCLOGIC_RDESC_PEN_PH(X_PM),
0x27, UCLOGIC_RDESC_PEN_PH(Y_LM),
UCLOGIC_RDESC_PEN_PH(Y_PM),
0x00, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM),
};
static const __u8 expected_pen_all_params[] = {
0xAA, 0x00, 0x00, 0x00,
0x47, 0xBB, 0x00, 0x00, 0x00,
0x27, 0xCC, 0x00, 0x00, 0x00,
0xDD, 0x00, 0x00, 0x00,
0x00, 0xEE, 0x00, 0x00, 0x00,
};
static const __u8 template_frame_all_params[] = {
0x01, 0x02,
UCLOGIC_RDESC_FRAME_PH_BTN,
0x99,
};
static const __u8 expected_frame_all_params[] = {
0x01, 0x02,
0x2A, 0xFF, 0x00,
0x99,
};
static const __u8 template_pen_some_params[] = {
0x01, 0x02,
UCLOGIC_RDESC_PEN_PH(X_LM),
0x03, UCLOGIC_RDESC_PEN_PH(X_PM),
0x04, 0x05,
};
static const __u8 expected_pen_some_params[] = {
0x01, 0x02,
0xAA, 0x00, 0x00, 0x00,
0x03, 0xBB, 0x00, 0x00, 0x00,
0x04, 0x05,
};
static const __u8 template_params_none[] = {
0x27, UCLOGIC_RDESC_PEN_PH(Y_LM),
UCLOGIC_RDESC_PEN_PH(Y_PM),
0x00, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM),
};
static struct uclogic_template_case uclogic_template_cases[] = {
{
.name = "Empty template",
.template = template_empty,
.template_size = sizeof(template_empty),
.param_list = params_pen_all,
.param_num = ARRAY_SIZE(params_pen_all),
.expected = template_empty,
},
{
.name = "Template smaller than the placeholder",
.template = template_small,
.template_size = sizeof(template_small),
.param_list = params_pen_all,
.param_num = ARRAY_SIZE(params_pen_all),
.expected = template_small,
},
{
.name = "No placeholder",
.template = template_no_ph,
.template_size = sizeof(template_no_ph),
.param_list = params_pen_all,
.param_num = ARRAY_SIZE(params_pen_all),
.expected = template_no_ph,
},
{
.name = "Pen placeholder at the end, without ID",
.template = template_pen_ph_end,
.template_size = sizeof(template_pen_ph_end),
.param_list = params_pen_all,
.param_num = ARRAY_SIZE(params_pen_all),
.expected = template_pen_ph_end,
},
{
.name = "Frame button placeholder at the end, without ID",
.template = template_btn_ph_end,
.template_size = sizeof(template_btn_ph_end),
.param_list = params_frame_all,
.param_num = ARRAY_SIZE(params_frame_all),
.expected = template_btn_ph_end,
},
{
.name = "All params present in the pen template",
.template = template_pen_all_params,
.template_size = sizeof(template_pen_all_params),
.param_list = params_pen_all,
.param_num = ARRAY_SIZE(params_pen_all),
.expected = expected_pen_all_params,
},
{
.name = "All params present in the frame template",
.template = template_frame_all_params,
.template_size = sizeof(template_frame_all_params),
.param_list = params_frame_all,
.param_num = ARRAY_SIZE(params_frame_all),
.expected = expected_frame_all_params,
},
{
.name = "Some params present in the pen template (complete param list)",
.template = template_pen_some_params,
.template_size = sizeof(template_pen_some_params),
.param_list = params_pen_all,
.param_num = ARRAY_SIZE(params_pen_all),
.expected = expected_pen_some_params,
},
{
.name = "Some params present in the pen template (incomplete param list)",
.template = template_pen_some_params,
.template_size = sizeof(template_pen_some_params),
.param_list = params_pen_some,
.param_num = ARRAY_SIZE(params_pen_some),
.expected = expected_pen_some_params,
},
{
.name = "No params present in the template",
.template = template_params_none,
.template_size = sizeof(template_params_none),
.param_list = params_pen_some,
.param_num = ARRAY_SIZE(params_pen_some),
.expected = template_params_none,
},
};
static void uclogic_template_case_desc(struct uclogic_template_case *t,
char *desc)
{
strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(uclogic_template, uclogic_template_cases,
uclogic_template_case_desc);
static void uclogic_template_test(struct kunit *test)
{
__u8 *res;
const struct uclogic_template_case *params = test->param_value;
res = uclogic_rdesc_template_apply(params->template,
params->template_size,
params->param_list,
params->param_num);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res);
KUNIT_EXPECT_EQ(test, 0,
memcmp(res, params->expected, params->template_size));
kfree(res);
}
static struct kunit_case hid_uclogic_rdesc_test_cases[] = {
KUNIT_CASE_PARAM(uclogic_template_test, uclogic_template_gen_params),
{}
};
static struct kunit_suite hid_uclogic_rdesc_test_suite = {
.name = "hid-uclogic-rdesc-test",
.test_cases = hid_uclogic_rdesc_test_cases,
};
kunit_test_suite(hid_uclogic_rdesc_test_suite);
MODULE_DESCRIPTION("KUnit tests for the UC-Logic driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("José Expósito <jose.exposito89@gmail.com>");

View File

@ -859,6 +859,108 @@ const __u8 uclogic_rdesc_v2_frame_dial_arr[] = {
const size_t uclogic_rdesc_v2_frame_dial_size = const size_t uclogic_rdesc_v2_frame_dial_size =
sizeof(uclogic_rdesc_v2_frame_dial_arr); sizeof(uclogic_rdesc_v2_frame_dial_arr);
/* Fixed report descriptor template for UGEE v2 pen reports */
const __u8 uclogic_rdesc_ugee_v2_pen_template_arr[] = {
0x05, 0x0d, /* Usage Page (Digitizers), */
0x09, 0x01, /* Usage (Digitizer), */
0xa1, 0x01, /* Collection (Application), */
0x85, 0x02, /* Report ID (2), */
0x09, 0x20, /* Usage (Stylus), */
0xa1, 0x00, /* Collection (Physical), */
0x09, 0x42, /* Usage (Tip Switch), */
0x09, 0x44, /* Usage (Barrel Switch), */
0x09, 0x46, /* Usage (Tablet Pick), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x03, /* Report Count (3), */
0x14, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x02, /* Report Count (2), */
0x81, 0x03, /* Input (Constant, Variable), */
0x09, 0x32, /* Usage (In Range), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x02, /* Report Count (2), */
0x81, 0x03, /* Input (Constant, Variable), */
0x75, 0x10, /* Report Size (16), */
0x95, 0x01, /* Report Count (1), */
0x35, 0x00, /* Physical Minimum (0), */
0xa4, /* Push, */
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x30, /* Usage (X), */
0x65, 0x13, /* Unit (Inch), */
0x55, 0x0d, /* Unit Exponent (-3), */
0x27, UCLOGIC_RDESC_PEN_PH(X_LM),
/* Logical Maximum (PLACEHOLDER), */
0x47, UCLOGIC_RDESC_PEN_PH(X_PM),
/* Physical Maximum (PLACEHOLDER), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x31, /* Usage (Y), */
0x27, UCLOGIC_RDESC_PEN_PH(Y_LM),
/* Logical Maximum (PLACEHOLDER), */
0x47, UCLOGIC_RDESC_PEN_PH(Y_PM),
/* Physical Maximum (PLACEHOLDER), */
0x81, 0x02, /* Input (Variable), */
0xb4, /* Pop, */
0x09, 0x30, /* Usage (Tip Pressure), */
0x45, 0x00, /* Physical Maximum (0), */
0x27, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM),
/* Logical Maximum (PLACEHOLDER), */
0x75, 0x0D, /* Report Size (13), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x03, /* Report Count (3), */
0x81, 0x01, /* Input (Constant), */
0x09, 0x3d, /* Usage (X Tilt), */
0x35, 0xC3, /* Physical Minimum (-61), */
0x45, 0x3C, /* Physical Maximum (60), */
0x15, 0xC3, /* Logical Minimum (-61), */
0x25, 0x3C, /* Logical Maximum (60), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x3e, /* Usage (Y Tilt), */
0x35, 0xC3, /* Physical Minimum (-61), */
0x45, 0x3C, /* Physical Maximum (60), */
0x15, 0xC3, /* Logical Minimum (-61), */
0x25, 0x3C, /* Logical Maximum (60), */
0x81, 0x02, /* Input (Variable), */
0xc0, /* End Collection, */
0xc0, /* End Collection */
};
const size_t uclogic_rdesc_ugee_v2_pen_template_size =
sizeof(uclogic_rdesc_ugee_v2_pen_template_arr);
/* Fixed report descriptor template for UGEE v2 frame reports (buttons only) */
const __u8 uclogic_rdesc_ugee_v2_frame_btn_template_arr[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x07, /* Usage (Keypad), */
0xA1, 0x01, /* Collection (Application), */
0x85, UCLOGIC_RDESC_V1_FRAME_ID,
/* Report ID, */
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x39, /* Usage (Tablet Function Keys), */
0xA0, /* Collection (Physical), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x08, /* Report Count (8), */
0x81, 0x01, /* Input (Constant), */
0x05, 0x09, /* Usage Page (Button), */
0x19, 0x01, /* Usage Minimum (01h), */
UCLOGIC_RDESC_FRAME_PH_BTN,
/* Usage Maximum (PLACEHOLDER), */
0x95, 0x0A, /* Report Count (10), */
0x14, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x46, /* Report Count (70), */
0x81, 0x01, /* Input (Constant), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
const size_t uclogic_rdesc_ugee_v2_frame_btn_template_size =
sizeof(uclogic_rdesc_ugee_v2_frame_btn_template_arr);
/* Fixed report descriptor for Ugee EX07 frame */ /* Fixed report descriptor for Ugee EX07 frame */
const __u8 uclogic_rdesc_ugee_ex07_frame_arr[] = { const __u8 uclogic_rdesc_ugee_ex07_frame_arr[] = {
0x05, 0x01, /* Usage Page (Desktop), */ 0x05, 0x01, /* Usage Page (Desktop), */
@ -979,7 +1081,7 @@ const size_t uclogic_rdesc_xppen_deco01_frame_size =
* uclogic_rdesc_template_apply() - apply report descriptor parameters to a * uclogic_rdesc_template_apply() - apply report descriptor parameters to a
* report descriptor template, creating a report descriptor. Copies the * report descriptor template, creating a report descriptor. Copies the
* template over to the new report descriptor and replaces every occurrence of * template over to the new report descriptor and replaces every occurrence of
* UCLOGIC_RDESC_PH_HEAD, followed by an index byte, with the value from the * the template placeholders, followed by an index byte, with the value from the
* parameter list at that index. * parameter list at that index.
* *
* @template_ptr: Pointer to the template buffer. * @template_ptr: Pointer to the template buffer.
@ -996,7 +1098,8 @@ __u8 *uclogic_rdesc_template_apply(const __u8 *template_ptr,
const s32 *param_list, const s32 *param_list,
size_t param_num) size_t param_num)
{ {
static const __u8 head[] = {UCLOGIC_RDESC_PH_HEAD}; static const __u8 btn_head[] = {UCLOGIC_RDESC_FRAME_PH_BTN_HEAD};
static const __u8 pen_head[] = {UCLOGIC_RDESC_PEN_PH_HEAD};
__u8 *rdesc_ptr; __u8 *rdesc_ptr;
__u8 *p; __u8 *p;
s32 v; s32 v;
@ -1005,12 +1108,19 @@ __u8 *uclogic_rdesc_template_apply(const __u8 *template_ptr,
if (rdesc_ptr == NULL) if (rdesc_ptr == NULL)
return NULL; return NULL;
for (p = rdesc_ptr; p + sizeof(head) < rdesc_ptr + template_size;) { for (p = rdesc_ptr; p + sizeof(btn_head) < rdesc_ptr + template_size;) {
if (memcmp(p, head, sizeof(head)) == 0 && if (p + sizeof(pen_head) < rdesc_ptr + template_size &&
p[sizeof(head)] < param_num) { memcmp(p, pen_head, sizeof(pen_head)) == 0 &&
v = param_list[p[sizeof(head)]]; p[sizeof(pen_head)] < param_num) {
v = param_list[p[sizeof(pen_head)]];
put_unaligned(cpu_to_le32(v), (s32 *)p); put_unaligned(cpu_to_le32(v), (s32 *)p);
p += sizeof(head) + 1; p += sizeof(pen_head) + 1;
} else if (memcmp(p, btn_head, sizeof(btn_head)) == 0 &&
p[sizeof(btn_head)] < param_num) {
v = param_list[p[sizeof(btn_head)]];
put_unaligned((__u8)0x2A, p); /* Usage Maximum */
put_unaligned_le16((__force u16)cpu_to_le16(v), p + 1);
p += sizeof(btn_head) + 1;
} else { } else {
p++; p++;
} }

View File

@ -81,7 +81,8 @@ extern __u8 uclogic_rdesc_twha60_fixed1_arr[];
extern const size_t uclogic_rdesc_twha60_fixed1_size; extern const size_t uclogic_rdesc_twha60_fixed1_size;
/* Report descriptor template placeholder head */ /* Report descriptor template placeholder head */
#define UCLOGIC_RDESC_PH_HEAD 0xFE, 0xED, 0x1D #define UCLOGIC_RDESC_PEN_PH_HEAD 0xFE, 0xED, 0x1D
#define UCLOGIC_RDESC_FRAME_PH_BTN_HEAD 0xFE, 0xED
/* Apply report descriptor parameters to a report descriptor template */ /* Apply report descriptor parameters to a report descriptor template */
extern __u8 *uclogic_rdesc_template_apply(const __u8 *template_ptr, extern __u8 *uclogic_rdesc_template_apply(const __u8 *template_ptr,
@ -89,19 +90,24 @@ extern __u8 *uclogic_rdesc_template_apply(const __u8 *template_ptr,
const s32 *param_list, const s32 *param_list,
size_t param_num); size_t param_num);
/* Pen report descriptor template placeholder IDs */ /* Report descriptor template placeholder IDs */
enum uclogic_rdesc_pen_ph_id { enum uclogic_rdesc_ph_id {
UCLOGIC_RDESC_PEN_PH_ID_X_LM, UCLOGIC_RDESC_PEN_PH_ID_X_LM,
UCLOGIC_RDESC_PEN_PH_ID_X_PM, UCLOGIC_RDESC_PEN_PH_ID_X_PM,
UCLOGIC_RDESC_PEN_PH_ID_Y_LM, UCLOGIC_RDESC_PEN_PH_ID_Y_LM,
UCLOGIC_RDESC_PEN_PH_ID_Y_PM, UCLOGIC_RDESC_PEN_PH_ID_Y_PM,
UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM, UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM,
UCLOGIC_RDESC_PEN_PH_ID_NUM UCLOGIC_RDESC_FRAME_PH_ID_UM,
UCLOGIC_RDESC_PH_ID_NUM
}; };
/* Report descriptor pen template placeholder */ /* Report descriptor pen template placeholder */
#define UCLOGIC_RDESC_PEN_PH(_ID) \ #define UCLOGIC_RDESC_PEN_PH(_ID) \
UCLOGIC_RDESC_PH_HEAD, UCLOGIC_RDESC_PEN_PH_ID_##_ID UCLOGIC_RDESC_PEN_PH_HEAD, UCLOGIC_RDESC_PEN_PH_ID_##_ID
/* Report descriptor frame buttons template placeholder */
#define UCLOGIC_RDESC_FRAME_PH_BTN \
UCLOGIC_RDESC_FRAME_PH_BTN_HEAD, UCLOGIC_RDESC_FRAME_PH_ID_UM
/* Report ID for v1 pen reports */ /* Report ID for v1 pen reports */
#define UCLOGIC_RDESC_V1_PEN_ID 0x07 #define UCLOGIC_RDESC_V1_PEN_ID 0x07
@ -155,6 +161,14 @@ extern const size_t uclogic_rdesc_v2_frame_dial_size;
/* Device ID byte offset in v2 frame dial reports */ /* Device ID byte offset in v2 frame dial reports */
#define UCLOGIC_RDESC_V2_FRAME_DIAL_DEV_ID_BYTE 0x4 #define UCLOGIC_RDESC_V2_FRAME_DIAL_DEV_ID_BYTE 0x4
/* Fixed report descriptor template for UGEE v2 pen reports */
extern const __u8 uclogic_rdesc_ugee_v2_pen_template_arr[];
extern const size_t uclogic_rdesc_ugee_v2_pen_template_size;
/* Fixed report descriptor template for UGEE v2 frame reports (buttons only) */
extern const __u8 uclogic_rdesc_ugee_v2_frame_btn_template_arr[];
extern const size_t uclogic_rdesc_ugee_v2_frame_btn_template_size;
/* Fixed report descriptor for Ugee EX07 frame */ /* Fixed report descriptor for Ugee EX07 frame */
extern const __u8 uclogic_rdesc_ugee_ex07_frame_arr[]; extern const __u8 uclogic_rdesc_ugee_ex07_frame_arr[];
extern const size_t uclogic_rdesc_ugee_ex07_frame_size; extern const size_t uclogic_rdesc_ugee_ex07_frame_size;

View File

@ -32,6 +32,21 @@ config I2C_HID_OF
will be called i2c-hid-of. It will also build/depend on the will be called i2c-hid-of. It will also build/depend on the
module i2c-hid. module i2c-hid.
config I2C_HID_OF_ELAN
tristate "Driver for Elan hid-i2c based devices on OF systems"
default n
depends on I2C && INPUT && OF
help
Say Y here if you want support for Elan i2c devices that use
the i2c-hid protocol on Open Firmware (Device Tree)-based
systems.
If unsure, say N.
This support is also available as a module. If so, the module
will be called i2c-hid-of-elan. It will also build/depend on
the module i2c-hid.
config I2C_HID_OF_GOODIX config I2C_HID_OF_GOODIX
tristate "Driver for Goodix hid-i2c based devices on OF systems" tristate "Driver for Goodix hid-i2c based devices on OF systems"
default n default n

View File

@ -10,4 +10,5 @@ i2c-hid-$(CONFIG_DMI) += i2c-hid-dmi-quirks.o
obj-$(CONFIG_I2C_HID_ACPI) += i2c-hid-acpi.o obj-$(CONFIG_I2C_HID_ACPI) += i2c-hid-acpi.o
obj-$(CONFIG_I2C_HID_OF) += i2c-hid-of.o obj-$(CONFIG_I2C_HID_OF) += i2c-hid-of.o
obj-$(CONFIG_I2C_HID_OF_ELAN) += i2c-hid-of-elan.o
obj-$(CONFIG_I2C_HID_OF_GOODIX) += i2c-hid-of-goodix.o obj-$(CONFIG_I2C_HID_OF_GOODIX) += i2c-hid-of-goodix.o

View File

@ -0,0 +1,130 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for Elan touchscreens that use the i2c-hid protocol.
*
* Copyright 2020 Google LLC
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm.h>
#include <linux/regulator/consumer.h>
#include "i2c-hid.h"
struct elan_i2c_hid_chip_data {
unsigned int post_gpio_reset_delay_ms;
unsigned int post_power_delay_ms;
u16 hid_descriptor_address;
};
struct i2c_hid_of_elan {
struct i2chid_ops ops;
struct regulator *vcc33;
struct regulator *vccio;
struct gpio_desc *reset_gpio;
const struct elan_i2c_hid_chip_data *chip_data;
};
static int elan_i2c_hid_power_up(struct i2chid_ops *ops)
{
struct i2c_hid_of_elan *ihid_elan =
container_of(ops, struct i2c_hid_of_elan, ops);
int ret;
ret = regulator_enable(ihid_elan->vcc33);
if (ret)
return ret;
ret = regulator_enable(ihid_elan->vccio);
if (ret) {
regulator_disable(ihid_elan->vcc33);
return ret;
}
if (ihid_elan->chip_data->post_power_delay_ms)
msleep(ihid_elan->chip_data->post_power_delay_ms);
gpiod_set_value_cansleep(ihid_elan->reset_gpio, 0);
if (ihid_elan->chip_data->post_gpio_reset_delay_ms)
msleep(ihid_elan->chip_data->post_gpio_reset_delay_ms);
return 0;
}
static void elan_i2c_hid_power_down(struct i2chid_ops *ops)
{
struct i2c_hid_of_elan *ihid_elan =
container_of(ops, struct i2c_hid_of_elan, ops);
gpiod_set_value_cansleep(ihid_elan->reset_gpio, 1);
regulator_disable(ihid_elan->vccio);
regulator_disable(ihid_elan->vcc33);
}
static int i2c_hid_of_elan_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_hid_of_elan *ihid_elan;
ihid_elan = devm_kzalloc(&client->dev, sizeof(*ihid_elan), GFP_KERNEL);
if (!ihid_elan)
return -ENOMEM;
ihid_elan->ops.power_up = elan_i2c_hid_power_up;
ihid_elan->ops.power_down = elan_i2c_hid_power_down;
/* Start out with reset asserted */
ihid_elan->reset_gpio =
devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(ihid_elan->reset_gpio))
return PTR_ERR(ihid_elan->reset_gpio);
ihid_elan->vccio = devm_regulator_get(&client->dev, "vccio");
if (IS_ERR(ihid_elan->vccio))
return PTR_ERR(ihid_elan->vccio);
ihid_elan->vcc33 = devm_regulator_get(&client->dev, "vcc33");
if (IS_ERR(ihid_elan->vcc33))
return PTR_ERR(ihid_elan->vcc33);
ihid_elan->chip_data = device_get_match_data(&client->dev);
return i2c_hid_core_probe(client, &ihid_elan->ops,
ihid_elan->chip_data->hid_descriptor_address, 0);
}
static const struct elan_i2c_hid_chip_data elan_ekth6915_chip_data = {
.post_power_delay_ms = 1,
.post_gpio_reset_delay_ms = 300,
.hid_descriptor_address = 0x0001,
};
static const struct of_device_id elan_i2c_hid_of_match[] = {
{ .compatible = "elan,ekth6915", .data = &elan_ekth6915_chip_data },
{ }
};
MODULE_DEVICE_TABLE(of, elan_i2c_hid_of_match);
static struct i2c_driver elan_i2c_hid_ts_driver = {
.driver = {
.name = "i2c_hid_of_elan",
.pm = &i2c_hid_core_pm,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
.of_match_table = of_match_ptr(elan_i2c_hid_of_match),
},
.probe = i2c_hid_of_elan_probe,
.remove = i2c_hid_core_remove,
.shutdown = i2c_hid_core_shutdown,
};
module_i2c_driver(elan_i2c_hid_ts_driver);
MODULE_AUTHOR("Douglas Anderson <dianders@chromium.org>");
MODULE_DESCRIPTION("Elan i2c-hid touchscreen driver");
MODULE_LICENSE("GPL");

View File

@ -578,7 +578,7 @@ static void _ish_sync_fw_clock(struct ishtp_device *dev)
static unsigned long prev_sync; static unsigned long prev_sync;
uint64_t usec; uint64_t usec;
if (prev_sync && jiffies - prev_sync < 20 * HZ) if (prev_sync && time_before(jiffies, prev_sync + 20 * HZ))
return; return;
prev_sync = jiffies; prev_sync = jiffies;

View File

@ -328,7 +328,7 @@ do_get_report:
/** /**
* ish_cl_event_cb() - bus driver callback for incoming message/packet * ish_cl_event_cb() - bus driver callback for incoming message/packet
* @device: Pointer to the the ishtp client device for which this message * @device: Pointer to the ishtp client device for which this message
* is targeted * is targeted
* *
* Remove the packet from the list and process the message by calling * Remove the packet from the list and process the message by calling

View File

@ -91,6 +91,7 @@
#include <linux/leds.h> #include <linux/leds.h>
#include <linux/usb/input.h> #include <linux/usb/input.h>
#include <linux/power_supply.h> #include <linux/power_supply.h>
#include <linux/timer.h>
#include <asm/unaligned.h> #include <asm/unaligned.h>
/* /*
@ -167,6 +168,7 @@ struct wacom {
struct delayed_work init_work; struct delayed_work init_work;
struct wacom_remote *remote; struct wacom_remote *remote;
struct work_struct mode_change_work; struct work_struct mode_change_work;
struct timer_list idleprox_timer;
bool generic_has_leds; bool generic_has_leds;
struct wacom_leds { struct wacom_leds {
struct wacom_group_leds *groups; struct wacom_group_leds *groups;
@ -239,4 +241,5 @@ struct wacom_led *wacom_led_find(struct wacom *wacom, unsigned int group,
struct wacom_led *wacom_led_next(struct wacom *wacom, struct wacom_led *cur); struct wacom_led *wacom_led_next(struct wacom *wacom, struct wacom_led *cur);
int wacom_equivalent_usage(int usage); int wacom_equivalent_usage(int usage);
int wacom_initialize_leds(struct wacom *wacom); int wacom_initialize_leds(struct wacom *wacom);
void wacom_idleprox_timeout(struct timer_list *list);
#endif #endif

View File

@ -2121,7 +2121,7 @@ static int wacom_register_inputs(struct wacom *wacom)
error = wacom_setup_pad_input_capabilities(pad_input_dev, wacom_wac); error = wacom_setup_pad_input_capabilities(pad_input_dev, wacom_wac);
if (error) { if (error) {
/* no pad in use on this interface */ /* no pad events using this interface */
input_free_device(pad_input_dev); input_free_device(pad_input_dev);
wacom_wac->pad_input = NULL; wacom_wac->pad_input = NULL;
pad_input_dev = NULL; pad_input_dev = NULL;
@ -2781,6 +2781,7 @@ static int wacom_probe(struct hid_device *hdev,
INIT_WORK(&wacom->battery_work, wacom_battery_work); INIT_WORK(&wacom->battery_work, wacom_battery_work);
INIT_WORK(&wacom->remote_work, wacom_remote_work); INIT_WORK(&wacom->remote_work, wacom_remote_work);
INIT_WORK(&wacom->mode_change_work, wacom_mode_change_work); INIT_WORK(&wacom->mode_change_work, wacom_mode_change_work);
timer_setup(&wacom->idleprox_timer, &wacom_idleprox_timeout, TIMER_DEFERRABLE);
/* ask for the report descriptor to be loaded by HID */ /* ask for the report descriptor to be loaded by HID */
error = hid_parse(hdev); error = hid_parse(hdev);
@ -2821,6 +2822,7 @@ static void wacom_remove(struct hid_device *hdev)
cancel_work_sync(&wacom->battery_work); cancel_work_sync(&wacom->battery_work);
cancel_work_sync(&wacom->remote_work); cancel_work_sync(&wacom->remote_work);
cancel_work_sync(&wacom->mode_change_work); cancel_work_sync(&wacom->mode_change_work);
del_timer_sync(&wacom->idleprox_timer);
if (hdev->bus == BUS_BLUETOOTH) if (hdev->bus == BUS_BLUETOOTH)
device_remove_file(&hdev->dev, &dev_attr_speed); device_remove_file(&hdev->dev, &dev_attr_speed);

View File

@ -11,6 +11,7 @@
#include "wacom_wac.h" #include "wacom_wac.h"
#include "wacom.h" #include "wacom.h"
#include <linux/input/mt.h> #include <linux/input/mt.h>
#include <linux/jiffies.h>
/* resolution for penabled devices */ /* resolution for penabled devices */
#define WACOM_PL_RES 20 #define WACOM_PL_RES 20
@ -41,6 +42,43 @@ static int wacom_numbered_button_to_key(int n);
static void wacom_update_led(struct wacom *wacom, int button_count, int mask, static void wacom_update_led(struct wacom *wacom, int button_count, int mask,
int group); int group);
static void wacom_force_proxout(struct wacom_wac *wacom_wac)
{
struct input_dev *input = wacom_wac->pen_input;
wacom_wac->shared->stylus_in_proximity = 0;
input_report_key(input, BTN_TOUCH, 0);
input_report_key(input, BTN_STYLUS, 0);
input_report_key(input, BTN_STYLUS2, 0);
input_report_key(input, BTN_STYLUS3, 0);
input_report_key(input, wacom_wac->tool[0], 0);
if (wacom_wac->serial[0]) {
input_report_abs(input, ABS_MISC, 0);
}
input_report_abs(input, ABS_PRESSURE, 0);
wacom_wac->tool[0] = 0;
wacom_wac->id[0] = 0;
wacom_wac->serial[0] = 0;
input_sync(input);
}
void wacom_idleprox_timeout(struct timer_list *list)
{
struct wacom *wacom = from_timer(wacom, list, idleprox_timer);
struct wacom_wac *wacom_wac = &wacom->wacom_wac;
if (!wacom_wac->hid_data.sense_state) {
return;
}
hid_warn(wacom->hdev, "%s: tool appears to be hung in-prox. forcing it out.\n", __func__);
wacom_force_proxout(wacom_wac);
}
/* /*
* Percent of battery capacity for Graphire. * Percent of battery capacity for Graphire.
* 8th value means AC online and show 100% capacity. * 8th value means AC online and show 100% capacity.
@ -638,9 +676,26 @@ static int wacom_intuos_id_mangle(int tool_id)
return (tool_id & ~0xFFF) << 4 | (tool_id & 0xFFF); return (tool_id & ~0xFFF) << 4 | (tool_id & 0xFFF);
} }
static bool wacom_is_art_pen(int tool_id)
{
bool is_art_pen = false;
switch (tool_id) {
case 0x885: /* Intuos3 Marker Pen */
case 0x804: /* Intuos4/5 13HD/24HD Marker Pen */
case 0x10804: /* Intuos4/5 13HD/24HD Art Pen */
is_art_pen = true;
break;
}
return is_art_pen;
}
static int wacom_intuos_get_tool_type(int tool_id) static int wacom_intuos_get_tool_type(int tool_id)
{ {
int tool_type; int tool_type = BTN_TOOL_PEN;
if (wacom_is_art_pen(tool_id))
return tool_type;
switch (tool_id) { switch (tool_id) {
case 0x812: /* Inking pen */ case 0x812: /* Inking pen */
@ -655,12 +710,9 @@ static int wacom_intuos_get_tool_type(int tool_id)
case 0x852: case 0x852:
case 0x823: /* Intuos3 Grip Pen */ case 0x823: /* Intuos3 Grip Pen */
case 0x813: /* Intuos3 Classic Pen */ case 0x813: /* Intuos3 Classic Pen */
case 0x885: /* Intuos3 Marker Pen */
case 0x802: /* Intuos4/5 13HD/24HD General Pen */ case 0x802: /* Intuos4/5 13HD/24HD General Pen */
case 0x804: /* Intuos4/5 13HD/24HD Marker Pen */
case 0x8e2: /* IntuosHT2 pen */ case 0x8e2: /* IntuosHT2 pen */
case 0x022: case 0x022:
case 0x10804: /* Intuos4/5 13HD/24HD Art Pen */
case 0x10842: /* MobileStudio Pro Pro Pen slim */ case 0x10842: /* MobileStudio Pro Pro Pen slim */
case 0x14802: /* Intuos4/5 13HD/24HD Classic Pen */ case 0x14802: /* Intuos4/5 13HD/24HD Classic Pen */
case 0x16802: /* Cintiq 13HD Pro Pen */ case 0x16802: /* Cintiq 13HD Pro Pen */
@ -718,10 +770,6 @@ static int wacom_intuos_get_tool_type(int tool_id)
case 0x10902: /* Intuos4/5 13HD/24HD Airbrush */ case 0x10902: /* Intuos4/5 13HD/24HD Airbrush */
tool_type = BTN_TOOL_AIRBRUSH; tool_type = BTN_TOOL_AIRBRUSH;
break; break;
default: /* Unknown tool */
tool_type = BTN_TOOL_PEN;
break;
} }
return tool_type; return tool_type;
} }
@ -2009,7 +2057,6 @@ static void wacom_wac_pad_usage_mapping(struct hid_device *hdev,
wacom_wac->has_mute_touch_switch = true; wacom_wac->has_mute_touch_switch = true;
usage->type = EV_SW; usage->type = EV_SW;
usage->code = SW_MUTE_DEVICE; usage->code = SW_MUTE_DEVICE;
features->device_type |= WACOM_DEVICETYPE_PAD;
break; break;
case WACOM_HID_WD_TOUCHSTRIP: case WACOM_HID_WD_TOUCHSTRIP:
wacom_map_usage(input, usage, field, EV_ABS, ABS_RX, 0); wacom_map_usage(input, usage, field, EV_ABS, ABS_RX, 0);
@ -2089,6 +2136,30 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field
wacom_wac->hid_data.inrange_state |= value; wacom_wac->hid_data.inrange_state |= value;
} }
/* Process touch switch state first since it is reported through touch interface,
* which is indepentent of pad interface. In the case when there are no other pad
* events, the pad interface will not even be created.
*/
if ((equivalent_usage == WACOM_HID_WD_MUTE_DEVICE) ||
(equivalent_usage == WACOM_HID_WD_TOUCHONOFF)) {
if (wacom_wac->shared->touch_input) {
bool *is_touch_on = &wacom_wac->shared->is_touch_on;
if (equivalent_usage == WACOM_HID_WD_MUTE_DEVICE && value)
*is_touch_on = !(*is_touch_on);
else if (equivalent_usage == WACOM_HID_WD_TOUCHONOFF)
*is_touch_on = value;
input_report_switch(wacom_wac->shared->touch_input,
SW_MUTE_DEVICE, !(*is_touch_on));
input_sync(wacom_wac->shared->touch_input);
}
return;
}
if (!input)
return;
switch (equivalent_usage) { switch (equivalent_usage) {
case WACOM_HID_WD_TOUCHRING: case WACOM_HID_WD_TOUCHRING:
/* /*
@ -2124,22 +2195,6 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field
input_event(input, usage->type, usage->code, 0); input_event(input, usage->type, usage->code, 0);
break; break;
case WACOM_HID_WD_MUTE_DEVICE:
case WACOM_HID_WD_TOUCHONOFF:
if (wacom_wac->shared->touch_input) {
bool *is_touch_on = &wacom_wac->shared->is_touch_on;
if (equivalent_usage == WACOM_HID_WD_MUTE_DEVICE && value)
*is_touch_on = !(*is_touch_on);
else if (equivalent_usage == WACOM_HID_WD_TOUCHONOFF)
*is_touch_on = value;
input_report_switch(wacom_wac->shared->touch_input,
SW_MUTE_DEVICE, !(*is_touch_on));
input_sync(wacom_wac->shared->touch_input);
}
break;
case WACOM_HID_WD_MODE_CHANGE: case WACOM_HID_WD_MODE_CHANGE:
if (wacom_wac->is_direct_mode != value) { if (wacom_wac->is_direct_mode != value) {
wacom_wac->is_direct_mode = value; wacom_wac->is_direct_mode = value;
@ -2312,6 +2367,7 @@ static void wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field
value = field->logical_maximum - value; value = field->logical_maximum - value;
break; break;
case HID_DG_INRANGE: case HID_DG_INRANGE:
mod_timer(&wacom->idleprox_timer, jiffies + msecs_to_jiffies(100));
wacom_wac->hid_data.inrange_state = value; wacom_wac->hid_data.inrange_state = value;
if (!(features->quirks & WACOM_QUIRK_SENSE)) if (!(features->quirks & WACOM_QUIRK_SENSE))
wacom_wac->hid_data.sense_state = value; wacom_wac->hid_data.sense_state = value;
@ -2336,6 +2392,9 @@ static void wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field
} }
return; return;
case HID_DG_TWIST: case HID_DG_TWIST:
/* don't modify the value if the pen doesn't support the feature */
if (!wacom_is_art_pen(wacom_wac->id[0])) return;
/* /*
* Userspace expects pen twist to have its zero point when * Userspace expects pen twist to have its zero point when
* the buttons/finger is on the tablet's left. HID values * the buttons/finger is on the tablet's left. HID values
@ -2822,7 +2881,7 @@ void wacom_wac_event(struct hid_device *hdev, struct hid_field *field,
/* usage tests must precede field tests */ /* usage tests must precede field tests */
if (WACOM_BATTERY_USAGE(usage)) if (WACOM_BATTERY_USAGE(usage))
wacom_wac_battery_event(hdev, field, usage, value); wacom_wac_battery_event(hdev, field, usage, value);
else if (WACOM_PAD_FIELD(field) && wacom->wacom_wac.pad_input) else if (WACOM_PAD_FIELD(field))
wacom_wac_pad_event(hdev, field, usage, value); wacom_wac_pad_event(hdev, field, usage, value);
else if (WACOM_PEN_FIELD(field) && wacom->wacom_wac.pen_input) else if (WACOM_PEN_FIELD(field) && wacom->wacom_wac.pen_input)
wacom_wac_pen_event(hdev, field, usage, value); wacom_wac_pen_event(hdev, field, usage, value);