HID: external sensor driver

HID driver to handle sensor data from external device
and writes it in to shared memory. Sensor data includes
temperature, accelerometer, gyroscope, magnetometer data.

Change-Id: I1018aba9ee3c83e88299e476fc0bcbb2a13f6314
Signed-off-by: Rohit Bandi <rohitbandi@codeaurora.org>
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 8eed456..9deed4f 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -966,6 +966,13 @@
 	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 IMU data for QVR support.
+
 endmenu
 
 endif # HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 86b2b57..5bcb6d2 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -78,6 +78,7 @@
 
 obj-$(CONFIG_HID_PLANTRONICS)	+= hid-plantronics.o
 obj-$(CONFIG_HID_PRIMAX)	+= hid-primax.o
+obj-$(CONFIG_HID_QVR)		+= hid-qvr.o
 obj-$(CONFIG_HID_ROCCAT)	+= hid-roccat.o hid-roccat-common.o \
 	hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \
 	hid-roccat-koneplus.o hid-roccat-konepure.o hid-roccat-kovaplus.o \
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index aea6267..350f2ba 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2018,6 +2018,9 @@
 	{ HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) },
+#if IS_ENABLED(CONFIG_HID_QVR)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_QVR5, USB_DEVICE_ID_QVR5) },
+#endif
 	{ HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU, USB_DEVICE_ID_RI_KA_WEBMAIL) },
 #if IS_ENABLED(CONFIG_HID_ROCCAT)
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ARVO) },
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 9347b37..95934a8 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1125,4 +1125,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
diff --git a/drivers/hid/hid-qvr.c b/drivers/hid/hid-qvr.c
new file mode 100644
index 0000000..019dbaf
--- /dev/null
+++ b/drivers/hid/hid-qvr.c
@@ -0,0 +1,414 @@
+/*
+ * Copyright (c) 2018, 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/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/timekeeping.h>
+#include <linux/ion.h>
+#include "../soc/qcom/smp2p_private.h"
+#include "hid-ids.h"
+#include "hid-qvr.h"
+
+static struct ion_handle *handle;
+static struct ion_client *client;
+static void *vaddr;
+static size_t vsize;
+static uint64_t ts_base;
+static uint64_t ts_offset;
+static int msg_size = 368;
+
+struct gpio_info {
+	int gpio_base_id;
+	int irq_base_id;
+};
+
+
+/* GPIO Inbound/Outbound callback info */
+struct gpio_inout {
+	struct gpio_info in;
+	struct gpio_info out;
+};
+
+static struct gpio_inout gpio_info[SMP2P_NUM_PROCS];
+static struct gpio_info *in_gpio_info_ptr;
+static struct gpio_info *out_gpio_info_ptr;
+
+
+static struct hid_driver qvr_external_sensor_driver;
+static int fd;
+
+
+struct ion_handle {
+	struct kref ref;
+	unsigned int user_ref_count;
+	struct ion_client *client;
+	struct ion_buffer *buffer;
+	struct rb_node node;
+	unsigned int kmap_cnt;
+	int id;
+};
+
+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);
+	if (imuData.mts0 == 0)
+		data->mts = 0;
+	else
+		data->mts = data->ats;
+	data->gts = 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;
+
+	pr_debug("%s: gts= %llu, gx= %d, gy=%d, gz=%d", __func__,
+		data->gts, data->gx, data->gy, data->gz);
+	pr_debug("%s: ats= %llu, ax= %d, ay=%d, az=%d", __func__,
+		data->ats, data->ax, data->ay, data->az);
+	pr_debug("%s: mts= %llu, mx= %d, my=%d, mz=%d", __func__,
+		data->mts, data->mx, data->my, data->mz);
+
+	index_buf->most_recent_index = buf_index;
+	buf_index = (buf_index == (8 - 1)) ? 0 : buf_index + 1;
+	return 0;
+}
+
+static int register_smp2p(char *node_name, struct gpio_info *gpio_info_ptr)
+{
+	struct device_node *node = NULL;
+	int cnt = 0;
+	int id = 0;
+
+	node = of_find_compatible_node(NULL, NULL, node_name);
+	if (node) {
+		cnt = of_gpio_count(node);
+		if (cnt && gpio_info_ptr) {
+			id = of_get_gpio(node, 0);
+			if (id == -EPROBE_DEFER)
+				return id;
+			gpio_info_ptr->gpio_base_id = id;
+			gpio_info_ptr->irq_base_id = gpio_to_irq(id);
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+
+
+static int kernel_map_gyro_buffer(int fd)
+{
+	handle = ion_import_dma_buf_fd(client, fd);
+	if (IS_ERR(handle)) {
+		pr_err("%s: ion_import_dma_buf_fd failed\n", __func__);
+		return -EINVAL;
+	}
+
+	if (ion_handle_get_size(client, handle, &vsize)) {
+		pr_err("%s: Could not dma buf %d size\n", __func__, fd);
+		return -EINVAL;
+	}
+
+	vaddr = ion_map_kernel(client, handle);
+	if (IS_ERR_OR_NULL(vaddr)) {
+		ion_free(client, handle);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+
+static void kernel_unmap_gyro_buffer(void)
+{
+	if (!IS_ERR_OR_NULL(vaddr)) {
+		ion_unmap_kernel(client, handle);
+		ion_free(client, handle);
+		vaddr = 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 *in_node_name = "qcom,smp2pgpio_client_qvrexternal_5_in";
+	char *out_node_name = "qcom,smp2pgpio_client_qvrexternal_5_out";
+	__u8 hid_buf[255] = { 0 };
+	size_t hid_count = 64;
+
+	ret = register_smp2p(in_node_name, in_gpio_info_ptr);
+	if (ret) {
+		pr_err("%s: register_smp2p failed", __func__);
+		goto err_free;
+	}
+	ret = register_smp2p(out_node_name, out_gpio_info_ptr);
+	if (ret) {
+		pr_err("%s: register_smp2p failed", __func__);
+		goto err_free;
+	}
+	ret = hid_open_report(hdev);
+	if (ret) {
+		pr_err("%s: hid_open_report failed", __func__);
+		goto err_free;
+	}
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		pr_err("%s: hid_hw_start failed", __func__);
+		goto err_free;
+	}
+	if (hdev->vendor == USB_VENDOR_ID_QVR5) {
+		hid_buf[0] = 2;
+		hid_buf[1] = 7;
+		ret = hid_hw_raw_request(hdev, hid_buf[0],
+			hid_buf,
+			hid_count,
+			HID_FEATURE_REPORT,
+			HID_REQ_SET_REPORT);
+	}
+	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)
+{
+	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) {
+			pr_err("%s: qvr_send_package_wrap failed", __func__);
+			return ret;
+		}
+		val = 1 ^ gpio_get_value(out_gpio_info_ptr->gpio_base_id + 0);
+		gpio_set_value(out_gpio_info_ptr->gpio_base_id + 0, 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)
+{
+	const char *device_name = "aoe";
+	int ret = 0;
+
+	in_gpio_info_ptr = &gpio_info[SMP2P_CDSP_PROC].in;
+	in_gpio_info_ptr->gpio_base_id = -1;
+	out_gpio_info_ptr = &gpio_info[SMP2P_CDSP_PROC].out;
+	out_gpio_info_ptr->gpio_base_id = -1;
+
+
+	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;
+	}
+
+	client = msm_ion_client_create(device_name);
+	if (client == NULL) {
+		pr_err("msm_ion_client_create failed in %s", __func__);
+		return -EINVAL;
+	}
+
+	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 0000000..3507d07
--- /dev/null
+++ b/drivers/hid/hid-qvr.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2018, 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/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c
index 1916f80..8bc8faa 100644
--- a/drivers/hid/usbhid/hid-quirks.c
+++ b/drivers/hid/usbhid/hid-quirks.c
@@ -170,6 +170,7 @@
 	{ USB_VENDOR_ID_MULTIPLE_1781, USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD, HID_QUIRK_MULTI_INPUT },
 	{ USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_2NES2SNES, HID_QUIRK_MULTI_INPUT },
 	{ USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_4NES4SNES, HID_QUIRK_MULTI_INPUT },
+	{ USB_VENDOR_ID_QVR5, USB_DEVICE_ID_QVR5, HID_QUIRK_HIDINPUT_FORCE },
 
 	{ 0, 0 }
 };