Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 83b24418 authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

Merge "HID: external sensor driver"

parents 0d1da8d7 a8d7836a
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -1079,6 +1079,15 @@ 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
	Support for streaming sensor data from an external device and to
	retrieve the calibration data of the associated device.
	Say 'Y' or 'M' if you want to connect an external device to
	stream sensor data for QVR support.

endmenu

endif # HID
+2 −0
Original line number Diff line number Diff line
@@ -128,3 +128,5 @@ obj-$(CONFIG_USB_KBD) += usbhid/
obj-$(CONFIG_I2C_HID)		+= i2c-hid/

obj-$(CONFIG_INTEL_ISH_HID)	+= intel-ish-hid/

obj-$(CONFIG_HID_QVR)		+= hid-qvr.o
+3 −0
Original line number Diff line number Diff line
@@ -1227,4 +1227,7 @@
#define USB_VENDOR_ID_UGTIZER			0x2179
#define USB_DEVICE_ID_UGTIZER_TABLET_GP0610	0x0053

#define USB_VENDOR_ID_QVR5	0x045e
#define USB_DEVICE_ID_QVR5	0x0659

#endif
+5 −0
Original line number Diff line number Diff line
@@ -168,6 +168,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 }
};
@@ -711,6 +713,9 @@ static const struct hid_device_id hid_have_special_driver[] = {
#endif
#if IS_ENABLED(CONFIG_HID_ZYDACRON)
	{ HID_USB_DEVICE(USB_VENDOR_ID_ZYDACRON, USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL) },
#endif
#if IS_ENABLED(CONFIG_HID_QVR)
	{ HID_USB_DEVICE(USB_VENDOR_ID_QVR5, USB_DEVICE_ID_QVR5) },
#endif
	{ }
};

drivers/hid/hid-qvr.c

0 → 100644
+393 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2018-2019, 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 <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/dma-buf.h>
#include <linux/msm_ion.h>
#include <linux/usb.h>
#include <linux/slab.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/input.h>
#include <linux/hiddev.h>
#include <linux/hid-debug.h>
#include <linux/hidraw.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/spinlock.h>
#include <linux/timekeeping.h>
#include <linux/soc/qcom/smem_state.h>
#include "hid-ids.h"
#include "hid-qvr.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;

	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");
Loading