diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 1ecb5124421c..3e46f752d07e 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1144,6 +1144,18 @@ config HID_ALPS Say Y here if you have a Alps touchpads over i2c-hid or usbhid and want support for its special functionalities. +config HID_QVR + tristate "QVR support" + depends on HID + help + Say 'Y' or 'M' if you want to connect an external device to + stream sensor data for QVR support. + + This module handles sensor data from external device and + writes it in to shared memory. Sensor data includes + temperature, accelerometer, gyroscope, magnetometer data. + + If unsure, say Y. endmenu endif # HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 0c03308cfb08..4f246234af50 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -138,3 +138,5 @@ obj-$(CONFIG_I2C_HID) += i2c-hid/ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ + +obj-$(CONFIG_HID_QVR) += hid-qvr.o hid-trace.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 447e8db21174..b7b883b6ddcc 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1273,4 +1273,7 @@ #define USB_VENDOR_ID_SIGNOTEC 0x2133 #define USB_DEVICE_ID_SIGNOTEC_VIEWSONIC_PD1011 0x0018 +#define USB_VENDOR_ID_QVR5 0x045e +#define USB_DEVICE_ID_QVR5 0x0659 + #endif diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index c50bcd967d99..d835e44dfefe 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -173,6 +173,8 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD2, USB_DEVICE_ID_SMARTJOY_DUAL_PLUS), HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_QUAD_USB_JOYPAD), HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT }, + { HID_USB_DEVICE(USB_VENDOR_ID_QVR5, USB_DEVICE_ID_QVR5), + HID_QUIRK_HIDINPUT_FORCE | HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { 0 } }; @@ -561,6 +563,9 @@ static const struct hid_device_id hid_have_special_driver[] = { #if IS_ENABLED(CONFIG_HID_PRODIKEYS) { HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_PRODIKEYS_PCMIDI) }, #endif +#if IS_ENABLED(CONFIG_HID_QVR) + { HID_USB_DEVICE(USB_VENDOR_ID_QVR5, USB_DEVICE_ID_QVR5) }, +#endif #if IS_ENABLED(CONFIG_HID_RETRODE) { HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY, USB_DEVICE_ID_RETRODE2) }, #endif diff --git a/drivers/hid/hid-qvr.c b/drivers/hid/hid-qvr.c new file mode 100644 index 000000000000..b17ab1e9bc1d --- /dev/null +++ b/drivers/hid/hid-qvr.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hid-ids.h" +#include "hid-qvr.h" +#include "hid-trace.h" + +static struct dma_buf *qvr_buf; +static void *vaddr; +static size_t vsize; +static uint64_t ts_base; +static uint64_t ts_offset; + +struct gpio_info { + unsigned int smem_bit; + struct qcom_smem_state *smem_state; +}; + + +static struct device *qvr_device; +static struct gpio_info gpio_info_out; + +static struct hid_driver qvr_external_sensor_driver; +static int fd; + +const static int msg_size = 368; +const static int hid_request_report_id = 2; +const static int hid_request_report_size = 64; + +struct qvr_buf_index { + int most_recent_index; + uint8_t padding[60]; +}; + +struct qvr_sensor_t { + uint64_t gts; + uint64_t ats; + uint64_t mts; + s32 gx; + s32 gy; + s32 gz; + s32 ax; + s32 ay; + s32 az; + s32 mx; + s32 my; + s32 mz; + uint8_t padding[4]; +}; + + +int qvr_send_package_wrap(u8 *message, int msize, struct hid_device *hid) +{ + struct qvr_sensor_t *sensor_buf; + struct qvr_sensor_t *data; + static int buf_index; + struct external_imu_format imuData = { 0 }; + struct qvr_buf_index *index_buf; + + /* + * Actual message size is 369 bytes + * to make it 8 byte aligned we created a structure of size 368 bytes. + * Ignoring the first byte 'report id' (which is always 1) + * + */ + memcpy((void *)&imuData, (void *)message + 1, msg_size); + + if (!ts_base) + ts_base = ktime_to_ns(ktime_get_boottime()); + if (!ts_offset) + ts_offset = imuData.gts0; + index_buf = (struct qvr_buf_index *) + ((uintptr_t)vaddr + (vsize / 2) + (8 * sizeof(*sensor_buf))); + sensor_buf = (struct qvr_sensor_t *)((uintptr_t)vaddr + (vsize / 2)); + + data = (struct qvr_sensor_t *)&(sensor_buf[buf_index]); + if (ts_offset > imuData.gts0) + data->ats = ts_base + ((ts_offset - imuData.gts0) * 100); + else + data->ats = ts_base + ((imuData.gts0 - ts_offset) * 100); + data->gts = data->ats; + data->mts = data->ats; + data->ax = -imuData.ax0; + data->ay = imuData.ay0; + data->az = -imuData.az0; + data->gx = -imuData.gx0; + data->gy = imuData.gy0; + data->gz = -imuData.gz0; + data->mx = -imuData.mx0; + data->my = imuData.my0; + data->mz = -imuData.mz0; + + trace_qvr_recv_sensor("gyro", data->gts, data->gx, data->gy, data->gz); + trace_qvr_recv_sensor("accel", data->ats, data->ax, data->ay, data->az); + + index_buf->most_recent_index = buf_index; + buf_index = (buf_index == (8 - 1)) ? 0 : buf_index + 1; + return 0; +} + +static int register_smp2p(struct device *dev, char *node_name, + struct gpio_info *gpio_info_ptr) +{ + struct device_node *node = dev->of_node; + + if (!gpio_info_ptr) + return -EINVAL; + if (node == NULL) { + pr_debug("%s: device node NULL\n", __func__); + dev->of_node = of_find_compatible_node(NULL, NULL, node_name); + node = dev->of_node; + } + if (!of_find_property(node, "qcom,smem-states", NULL)) + return -EINVAL; + gpio_info_ptr->smem_state = qcom_smem_state_get(dev, + "qvrexternal-smp2p-out", + &gpio_info_ptr->smem_bit); + pr_debug("%s: state: %pK, bit: %d\n", __func__, + gpio_info_ptr->smem_state, + gpio_info_ptr->smem_bit); + if (IS_ERR_OR_NULL(gpio_info_ptr->smem_state)) { + pr_debug("%s: Error smem_state\n", __func__); + return PTR_ERR(gpio_info_ptr->smem_state); + } + + return 0; + +} +static int kernel_map_gyro_buffer(int fd) +{ + int ret = 0; + + qvr_buf = dma_buf_get(fd); + if (IS_ERR_OR_NULL(qvr_buf)) { + ret = -ENOMEM; + pr_err("dma_buf_get failed for fd: %d\n", fd); + goto done; + } + ret = dma_buf_begin_cpu_access(qvr_buf, DMA_BIDIRECTIONAL); + if (ret) { + pr_err("%s: dma_buf_begin_cpu_access failed\n", __func__); + goto err_dma; + } + vsize = qvr_buf->size; + vaddr = dma_buf_kmap(qvr_buf, 0); + if (IS_ERR_OR_NULL(vaddr)) { + ret = -ENOMEM; + pr_err("dma_buf_kmap failed for fd: %d\n", fd); + goto err_end_access; + } + + return 0; + +err_end_access: + dma_buf_end_cpu_access(qvr_buf, DMA_BIDIRECTIONAL); +err_dma: + dma_buf_put(qvr_buf); + qvr_buf = NULL; +done: + return ret; + +} + + +static void kernel_unmap_gyro_buffer(void) +{ + if (IS_ERR_OR_NULL(vaddr)) + return; + dma_buf_kunmap(qvr_buf, 0, vaddr); + dma_buf_end_cpu_access(qvr_buf, DMA_BIDIRECTIONAL); + vaddr = NULL; + dma_buf_put(qvr_buf); + qvr_buf = NULL; +} + +static ssize_t fd_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return snprintf(buf, 16, "%d\n", fd); +} + +static ssize_t fd_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + ret = kstrtoint(buf, 10, &fd); + if (ret < 0) + return ret; + if (fd == -1) + kernel_unmap_gyro_buffer(); + else + kernel_map_gyro_buffer(fd); + ts_base = 0; + ts_offset = 0; + + return count; +} + +static ssize_t ts_base_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, 16, "%lld\n", ts_base); +} + +static ssize_t ts_base_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + return 0; +} + +static ssize_t ts_offset_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, 16, "%lld\n", ts_offset * 100); +} + +static ssize_t ts_offset_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + return 0; +} + +static struct kobj_attribute fd_attribute = __ATTR(fd, 0664, + fd_show, + fd_store); +static struct kobj_attribute ts_base_attribute = __ATTR(ts_base, 0664, + ts_base_show, + ts_base_store); +static struct kobj_attribute ts_offset_attribute = __ATTR(ts_offset, 0664, + ts_offset_show, + ts_offset_store); + +static struct attribute *attrs[] = { + &fd_attribute.attr, + &ts_base_attribute.attr, + &ts_offset_attribute.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = attrs, +}; + +static struct kobject *qvr_external_sensor_kobj; + +static int qvr_external_sensor_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + char *node_name = "qcom,smp2p-interrupt-qvrexternal-5-out"; + __u8 *hid_buf; + + ret = register_smp2p(&hdev->dev, node_name, &gpio_info_out); + if (ret) { + pr_err("%s: register_smp2p failed\n", __func__); + goto err_free; + } + ret = hid_open_report(hdev); + if (ret) { + pr_err("%s: hid_open_report failed\n", __func__); + goto err_free; + } + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + pr_err("%s: hid_hw_start failed\n", __func__); + goto err_free; + } + if (hdev->vendor == USB_VENDOR_ID_QVR5) { + hid_buf = kzalloc(255, GFP_ATOMIC); + if (hid_buf == NULL) + return -ENOMEM; + hid_buf[0] = hid_request_report_id; + hid_buf[1] = 7; + ret = hid_hw_raw_request(hdev, hid_buf[0], hid_buf, + hid_request_report_size, + HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + kfree(hid_buf); + } + + qvr_device = &hdev->dev; + + return 0; + +err_free: + return ret; + +} + +static int qvr_external_sensor_raw_event(struct hid_device *hid, + struct hid_report *report, + u8 *data, int size) +{ + static int val; + int ret = -1; + + if ((hid->vendor == USB_VENDOR_ID_QVR5) && (vaddr != NULL)) { + ret = qvr_send_package_wrap(data/*hid_value*/, size, hid); + if (ret == 0) { + val = 1 ^ val; + qcom_smem_state_update_bits(gpio_info_out.smem_state, + BIT(gpio_info_out.smem_bit), val); + ret = -1; + } + } + return ret; +} + +static void qvr_external_sensor_device_remove(struct hid_device *hdev) +{ + hid_hw_stop(hdev); +} + +static struct hid_device_id qvr_external_sensor_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_QVR5, USB_DEVICE_ID_QVR5) }, + { } +}; +MODULE_DEVICE_TABLE(hid, qvr_external_sensor_table); + +static struct hid_driver qvr_external_sensor_driver = { + .name = "qvr_external_sensor", + .id_table = qvr_external_sensor_table, + .probe = qvr_external_sensor_probe, + .raw_event = qvr_external_sensor_raw_event, + .remove = qvr_external_sensor_device_remove, +}; + +module_hid_driver(qvr_external_sensor_driver); + +static int __init qvr_external_sensor_init(void) +{ + int ret = 0; + + qvr_external_sensor_kobj = + kobject_create_and_add("qvr_external_sensor", kernel_kobj); + if (!qvr_external_sensor_kobj) { + pr_err("%s: kobject_create_and_add() fail\n", __func__); + return -ENOMEM; + } + ret = sysfs_create_group(qvr_external_sensor_kobj, &attr_group); + if (ret) { + pr_err("%s: can't register sysfs\n", __func__); + return -ENOMEM; + } + + return ret; +} + +static void __exit qvr_external_sensor_exit(void) +{ + kobject_put(qvr_external_sensor_kobj); +} + +module_init(qvr_external_sensor_init); +module_exit(qvr_external_sensor_exit); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/hid/hid-qvr.h b/drivers/hid/hid-qvr.h new file mode 100644 index 000000000000..bc0755d37745 --- /dev/null +++ b/drivers/hid/hid-qvr.h @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef HID_QVR_H_FILE +#define HID_QVR_H_FILE + +#define USB_VENDOR_ID_QVR5 0x045e +#define USB_DEVICE_ID_QVR5 0x0659 +#define QVR_EXTERNAL_SENSOR_REPORT_ID 0x1 + +struct external_imu_format { + s16 temp0; + s16 temp1; + s16 temp2; + s16 temp3; + u64 gts0; + u64 gts1; + u64 gts2; + u64 gts3; + s16 gx0; + s16 gx1; + s16 gx2; + s16 gx3; + s16 gx4; + s16 gx5; + s16 gx6; + s16 gx7; + s16 gx8; + s16 gx9; + s16 gx10; + s16 gx11; + s16 gx12; + s16 gx13; + s16 gx14; + s16 gx15; + s16 gx16; + s16 gx17; + s16 gx18; + s16 gx19; + s16 gx20; + s16 gx21; + s16 gx22; + s16 gx23; + s16 gx24; + s16 gx25; + s16 gx26; + s16 gx27; + s16 gx28; + s16 gx29; + s16 gx30; + s16 gx31; + s16 gy0; + s16 gy1; + s16 gy2; + s16 gy3; + s16 gy4; + s16 gy5; + s16 gy6; + s16 gy7; + s16 gy8; + s16 gy9; + s16 gy10; + s16 gy11; + s16 gy12; + s16 gy13; + s16 gy14; + s16 gy15; + s16 gy16; + s16 gy17; + s16 gy18; + s16 gy19; + s16 gy20; + s16 gy21; + s16 gy22; + s16 gy23; + s16 gy24; + s16 gy25; + s16 gy26; + s16 gy27; + s16 gy28; + s16 gy29; + s16 gy30; + s16 gy31; + s16 gz0; + s16 gz1; + s16 gz2; + s16 gz3; + s16 gz4; + s16 gz5; + s16 gz6; + s16 gz7; + s16 gz8; + s16 gz9; + s16 gz10; + s16 gz11; + s16 gz12; + s16 gz13; + s16 gz14; + s16 gz15; + s16 gz16; + s16 gz17; + s16 gz18; + s16 gz19; + s16 gz20; + s16 gz21; + s16 gz22; + s16 gz23; + s16 gz24; + s16 gz25; + s16 gz26; + s16 gz27; + s16 gz28; + s16 gz29; + s16 gz30; + s16 gz31; + u64 ats0; + u64 ats1; + u64 ats2; + u64 ats3; + s32 ax0; + s32 ax1; + s32 ax2; + s32 ax3; + s32 ay0; + s32 ay1; + s32 ay2; + s32 ay3; + s32 az0; + s32 az1; + s32 az2; + s32 az3; + u64 mts0; + u64 mts1; + u64 mts2; + u64 mts3; + s16 mx0; + s16 mx1; + s16 mx2; + s16 mx3; + s16 my0; + s16 my1; + s16 my2; + s16 my3; + s16 mz0; + s16 mz1; + s16 mz2; + s16 mz3; //368 bytes +}; + +int qvr_send_package_wrap(u8 *message, int msize, struct hid_device *hid); +void qvr_clear_def_parmeter(void); +void qvr_init(struct hid_device *hdev); +int qvr_input_init(void); +void qvr_input_remove(void); + +#endif diff --git a/drivers/hid/hid-trace.c b/drivers/hid/hid-trace.c new file mode 100644 index 000000000000..fca5193d4dcd --- /dev/null +++ b/drivers/hid/hid-trace.c @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +/* Instantiate tracepoints */ +#define CREATE_TRACE_POINTS +#include "hid-trace.h" diff --git a/drivers/hid/hid-trace.h b/drivers/hid/hid-trace.h new file mode 100644 index 000000000000..56dc89b3b1a5 --- /dev/null +++ b/drivers/hid/hid-trace.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#if !defined(_HID_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _HID_TRACE_H + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM hid +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE hid-trace + +#include + +TRACE_EVENT(qvr_recv_sensor, + TP_PROTO(char *sensor, uint64_t ts, s32 x, s32 y, s32 z), + TP_ARGS(sensor, ts, x, y, z), + TP_STRUCT__entry( + __field(char *, sensor) + __field(uint64_t, ts) + __field(int, x) + __field(int, y) + __field(int, z) + ), + TP_fast_assign( + __entry->sensor = sensor; + __entry->ts = ts; + __entry->x = x; + __entry->y = y; + __entry->z = z; + ), + TP_printk( + "%s - ts=%llu x=%d y=%d z=%d", + __entry->sensor, + __entry->ts, + __entry->x, + __entry->y, + __entry->z + ) + ); + +#endif /* _HID_TRACE_H */ + +/* This part must be outside protection */ +#include