[PATCH v8 3/4] SFH:Transport Driver to add support of AMD Sensor Fusion Hub (SFH)

From: Sandeep Singh
Date: Fri Oct 09 2020 - 16:05:29 EST


From: Sandeep Singh <sandeep.singh@xxxxxxx>

To support AMD Sensor Fusion Hub (SFH) via the HID client's we need to
register the client with the HID framework. Here we mostly address on
how to register the client with the framework and define the interfaces
for communication.

Co-developed-by: Nehal Shah <Nehal-bakulchandra.Shah@xxxxxxx>
Signed-off-by: Nehal Shah <Nehal-bakulchandra.Shah@xxxxxxx>
Signed-off-by: Sandeep Singh <sandeep.singh@xxxxxxx>
---
drivers/hid/amd-sfh-hid/amd_sfh_client.c | 246 +++++++++++++++++++++++
drivers/hid/amd-sfh-hid/amd_sfh_hid.c | 174 ++++++++++++++++
drivers/hid/amd-sfh-hid/amd_sfh_hid.h | 67 ++++++
3 files changed, 487 insertions(+)
create mode 100644 drivers/hid/amd-sfh-hid/amd_sfh_client.c
create mode 100644 drivers/hid/amd-sfh-hid/amd_sfh_hid.c
create mode 100644 drivers/hid/amd-sfh-hid/amd_sfh_hid.h

diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c b/drivers/hid/amd-sfh-hid/amd_sfh_client.c
new file mode 100644
index 000000000000..3d1ccac5d99a
--- /dev/null
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD SFH Client Layer
+ * Copyright 2020 Advanced Micro Devices, Inc.
+ * Authors: Nehal Bakulchandra Shah <Nehal-Bakulchandra.Shah@xxxxxxx>
+ * Sandeep Singh <Sandeep.singh@xxxxxxx>
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/hid.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/errno.h>
+
+#include "hid_descriptor/amd_sfh_hid_desc.h"
+#include "amd_sfh_pcie.h"
+#include "amd_sfh_hid.h"
+
+#define AMD_SFH_IDLE_LOOP 200
+
+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,
+ int report_type)
+{
+ struct amdtp_hid_data *hid_data = hid->driver_data;
+ struct amdtp_cl_data *cli_data = hid_data->cli_data;
+ int i;
+
+ for (i = 0; i < cli_data->num_hid_devices; i++) {
+ if (cli_data->hid_sensor_hubs[i] == hid) {
+ cli_data->cur_hid_dev = i;
+ break;
+ }
+ }
+ amdtp_hid_wakeup(hid);
+}
+
+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_cl_data *cli_data = hid_data->cli_data;
+ int i;
+
+ for (i = 0; i < cli_data->num_hid_devices; i++) {
+ if (cli_data->hid_sensor_hubs[i] == hid) {
+ struct request_list *new = kzalloc(sizeof(*new), GFP_KERNEL);
+
+ if (!new)
+ return -ENOMEM;
+
+ new->current_index = i;
+ new->sensor_idx = cli_data->sensor_idx[i];
+ new->hid = hid;
+ new->report_type = report_type;
+ new->report_id = report_id;
+ cli_data->report_id[i] = report_id;
+ cli_data->request_done[i] = false;
+ list_add(&new->list, &req_list.list);
+ break;
+ }
+ }
+ schedule_delayed_work(&cli_data->work, 0);
+ return 0;
+}
+
+static void amd_sfh_work(struct work_struct *work)
+{
+ struct amdtp_cl_data *cli_data = container_of(work, struct amdtp_cl_data, work.work);
+ struct request_list *req_node;
+ u8 current_index, sensor_index;
+ u8 report_id, node_type;
+ u8 report_size = 0;
+
+ req_node = list_last_entry(&req_list.list, struct request_list, list);
+ list_del(&req_node->list);
+ current_index = req_node->current_index;
+ sensor_index = req_node->sensor_idx;
+ report_id = req_node->report_id;
+ node_type = req_node->report_type;
+
+ if (node_type == HID_FEATURE_REPORT) {
+ report_size = get_feature_report(sensor_index, report_id,
+ cli_data->feature_report[current_index]);
+ if (report_size)
+ hid_input_report(cli_data->hid_sensor_hubs[current_index],
+ cli_data->report_type[current_index],
+ cli_data->feature_report[current_index], report_size, 0);
+ else
+ pr_err("AMDSFH: Invalid report size\n");
+
+ } else if (node_type == HID_INPUT_REPORT) {
+ report_size = get_input_report(sensor_index, report_id,
+ cli_data->input_report[current_index],
+ cli_data->sensor_virt_addr[current_index]);
+ if (report_size)
+ hid_input_report(cli_data->hid_sensor_hubs[current_index],
+ cli_data->report_type[current_index],
+ cli_data->input_report[current_index], report_size, 0);
+ else
+ pr_err("AMDSFH: Invalid report size\n");
+ }
+ cli_data->cur_hid_dev = current_index;
+ cli_data->sensor_requested_cnt[current_index] = 0;
+ amdtp_hid_wakeup(cli_data->hid_sensor_hubs[current_index]);
+}
+
+static 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);
+ u8 report_size;
+ int i;
+
+ for (i = 0; i < cli_data->num_hid_devices; i++) {
+ report_size = get_input_report(cli_data->sensor_idx[i], cli_data->report_id[i],
+ cli_data->input_report[i],
+ cli_data->sensor_virt_addr[i]);
+ hid_input_report(cli_data->hid_sensor_hubs[i], HID_INPUT_REPORT,
+ cli_data->input_report[i], report_size, 0);
+ }
+ schedule_delayed_work(&cli_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP));
+}
+
+int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
+{
+ struct amdtp_cl_data *cl_data = privdata->cl_data;
+ struct amd_mp2_sensor_info info;
+ struct device *dev;
+ u32 feature_report_size;
+ u32 input_report_size;
+ u8 cl_idx;
+ int rc, i;
+
+ dev = &privdata->pdev->dev;
+ cl_data = kzalloc(sizeof(*cl_data), GFP_KERNEL);
+ if (!cl_data)
+ return -ENOMEM;
+
+ cl_data->num_hid_devices = amd_mp2_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);
+
+ for (i = 0; i < cl_data->num_hid_devices; i++) {
+ cl_data->sensor_virt_addr[i] = dma_alloc_coherent(dev, sizeof(int) * 8,
+ &cl_data->sensor_phys_addr[i],
+ GFP_KERNEL);
+ cl_data->sensor_sts[i] = 0;
+ 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] = get_descr_sz(cl_idx, descr_size);
+ if (!cl_data->report_descr_sz[i]) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+ feature_report_size = get_descr_sz(cl_idx, feature_size);
+ if (!feature_report_size) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+ input_report_size = get_descr_sz(cl_idx, input_size);
+ if (!input_report_size) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+ cl_data->feature_report[i] = kzalloc(feature_report_size, GFP_KERNEL);
+ if (!cl_data->feature_report[i]) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+ cl_data->input_report[i] = kzalloc(input_report_size, GFP_KERNEL);
+ if (!cl_data->input_report[i]) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+ info.period = msecs_to_jiffies(AMD_SFH_IDLE_LOOP);
+ info.sensor_idx = cl_idx;
+ info.phys_address = cl_data->sensor_phys_addr[i];
+
+ cl_data->report_descr[i] = kzalloc(cl_data->report_descr_sz[i], GFP_KERNEL);
+ if (!cl_data->report_descr[i]) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+ rc = get_report_descriptor(cl_idx, cl_data->report_descr[i]);
+ if (rc)
+ return rc;
+ rc = amdtp_hid_probe(cl_data->cur_hid_dev, cl_data);
+ if (rc)
+ return rc;
+ amd_start_sensor(privdata, info);
+ cl_data->sensor_sts[i] = 1;
+ }
+ privdata->cl_data = cl_data;
+ schedule_delayed_work(&cl_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP));
+ return 0;
+
+cleanup:
+ for (i = 0; i < cl_data->num_hid_devices; i++) {
+ if (cl_data->sensor_virt_addr[i]) {
+ dma_free_coherent(&privdata->pdev->dev, 8 * sizeof(int),
+ cl_data->sensor_virt_addr[i],
+ cl_data->sensor_phys_addr[i]);
+ }
+ kfree(cl_data->feature_report[i]);
+ kfree(cl_data->input_report[i]);
+ kfree(cl_data->report_descr[i]);
+ }
+ kfree(cl_data);
+ return rc;
+}
+
+int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata)
+{
+ struct amdtp_cl_data *cl_data = privdata->cl_data;
+ int i;
+
+ for (i = 0; i < cl_data->num_hid_devices; i++)
+ amd_stop_sensor(privdata, i);
+
+ cancel_delayed_work_sync(&cl_data->work);
+ cancel_delayed_work_sync(&cl_data->work_buffer);
+ amdtp_hid_remove(cl_data);
+
+ for (i = 0; i < cl_data->num_hid_devices; i++) {
+ if (cl_data->sensor_virt_addr[i]) {
+ dma_free_coherent(&privdata->pdev->dev, 8 * sizeof(int),
+ cl_data->sensor_virt_addr[i],
+ cl_data->sensor_phys_addr[i]);
+ }
+ }
+ kfree(cl_data);
+ return 0;
+}
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c
new file mode 100644
index 000000000000..a471079a3bd0
--- /dev/null
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD MP2 Sensors transport driver
+ *
+ * Authors: Nehal Bakulchandra Shah <Nehal-bakulchandra.shah@xxxxxxx>
+ * Sandeep Singh <sandeep.singh@xxxxxxx>
+ */
+#include <linux/hid.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+
+#include "amd_sfh_hid.h"
+
+#define AMD_SFH_RESPONSE_TIMEOUT 1500
+
+/**
+ * amdtp_hid_parse() - hid-core .parse() callback
+ * @hid: hid device instance
+ *
+ * This function gets called during call to hid_add_device
+ *
+ * Return: 0 on success and non zero on error
+ */
+static int amdtp_hid_parse(struct hid_device *hid)
+{
+ struct amdtp_hid_data *hid_data = hid->driver_data;
+ struct amdtp_cl_data *cli_data = hid_data->cli_data;
+
+ return hid_parse_report(hid, cli_data->report_descr[hid_data->index],
+ cli_data->report_descr_sz[hid_data->index]);
+}
+
+/* Empty callbacks with success return code */
+static int amdtp_hid_start(struct hid_device *hid)
+{
+ return 0;
+}
+
+static void amdtp_hid_stop(struct hid_device *hid)
+{
+}
+
+static int amdtp_hid_open(struct hid_device *hid)
+{
+ return 0;
+}
+
+static void amdtp_hid_close(struct hid_device *hid)
+{
+}
+
+static int amdtp_raw_request(struct hid_device *hdev, u8 reportnum,
+ u8 *buf, size_t len, u8 rtype, int reqtype)
+{
+ return 0;
+}
+
+static void amdtp_hid_request(struct hid_device *hid, struct hid_report *rep, int reqtype)
+{
+ int rc;
+
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ rc = amd_sfh_get_report(hid, rep->id, rep->type);
+ if (rc)
+ dev_err(&hid->dev, "AMDSFH get report error\n");
+ break;
+ case HID_REQ_SET_REPORT:
+ amd_sfh_set_report(hid, rep->id, reqtype);
+ break;
+ default:
+ break;
+ }
+}
+
+static int amdtp_wait_for_response(struct hid_device *hid)
+{
+ struct amdtp_hid_data *hid_data = hid->driver_data;
+ struct amdtp_cl_data *cli_data = hid_data->cli_data;
+ int i, ret = 0;
+
+ for (i = 0; i < cli_data->num_hid_devices; i++) {
+ if (cli_data->hid_sensor_hubs[i] == hid)
+ break;
+ }
+
+ if (!cli_data->request_done[i])
+ ret = wait_event_interruptible_timeout(hid_data->hid_wait,
+ cli_data->request_done[i],
+ msecs_to_jiffies(AMD_SFH_RESPONSE_TIMEOUT));
+ if (ret < 0)
+ return -ETIMEDOUT;
+ else if (ret == -ERESTARTSYS)
+ return -ERESTARTSYS;
+ else
+ return 0;
+}
+
+void amdtp_hid_wakeup(struct hid_device *hid)
+{
+ struct amdtp_hid_data *hid_data = hid->driver_data;
+ struct amdtp_cl_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 = {
+ .parse = amdtp_hid_parse,
+ .start = amdtp_hid_start,
+ .stop = amdtp_hid_stop,
+ .open = amdtp_hid_open,
+ .close = amdtp_hid_close,
+ .request = amdtp_hid_request,
+ .wait = amdtp_wait_for_response,
+ .raw_request = amdtp_raw_request,
+};
+
+int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data)
+{
+ struct hid_device *hid;
+ struct amdtp_hid_data *hid_data;
+ int rc;
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid))
+ return PTR_ERR(hid);
+
+ hid_data = kzalloc(sizeof(*hid_data), GFP_KERNEL);
+ if (!hid_data) {
+ rc = -ENOMEM;
+ goto err_hid_data;
+ }
+
+ hid->ll_driver = &amdtp_hid_ll_driver;
+ hid_data->index = cur_hid_dev;
+ hid_data->cli_data = cli_data;
+ init_waitqueue_head(&hid_data->hid_wait);
+
+ hid->driver_data = hid_data;
+ cli_data->hid_sensor_hubs[cur_hid_dev] = hid;
+ hid->bus = BUS_AMD_AMDTP;
+ hid->vendor = AMD_SFH_HID_VENDOR;
+ hid->product = AMD_SFH_HID_PRODUCT;
+ snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "hid-amdtp",
+ hid->vendor, hid->product);
+
+ rc = hid_add_device(hid);
+ if (rc)
+ goto err_hid_device;
+ return 0;
+
+err_hid_device:
+ kfree(hid_data);
+err_hid_data:
+ hid_destroy_device(hid);
+ return rc;
+}
+
+void amdtp_hid_remove(struct amdtp_cl_data *cli_data)
+{
+ int i;
+
+ for (i = 0; i < cli_data->num_hid_devices; ++i) {
+ kfree(cli_data->feature_report[i]);
+ kfree(cli_data->input_report[i]);
+ kfree(cli_data->report_descr[i]);
+ if (cli_data->hid_sensor_hubs[i]) {
+ kfree(cli_data->hid_sensor_hubs[i]->driver_data);
+ hid_destroy_device(cli_data->hid_sensor_hubs[i]);
+ cli_data->hid_sensor_hubs[i] = NULL;
+ }
+ }
+}
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
new file mode 100644
index 000000000000..6be0783d885c
--- /dev/null
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * AMD MP2 Sensors transport driver
+ *
+ * Authors: Nehal Bakulchandra Shah <Nehal-bakulchandra.shah@xxxxxxx>
+ * Sandeep Singh <sandeep.singh@xxxxxxx>
+ */
+
+#ifndef AMDSFH_HID_H
+#define AMDSFH_HID_H
+
+#define MAX_HID_DEVICES 4
+#define BUS_AMD_AMDTP 0x20
+#define AMD_SFH_HID_VENDOR 0x1022
+#define AMD_SFH_HID_PRODUCT 0x0001
+
+struct amdtp_cl_data {
+ u8 init_done;
+ u32 cur_hid_dev;
+ u32 hid_dev_count;
+ u32 num_hid_devices;
+ struct device_info *hid_devices;
+ u8 *report_descr[MAX_HID_DEVICES];
+ int report_descr_sz[MAX_HID_DEVICES];
+ struct hid_device *hid_sensor_hubs[MAX_HID_DEVICES];
+ u8 *hid_descr[MAX_HID_DEVICES];
+ int hid_descr_size[MAX_HID_DEVICES];
+ phys_addr_t phys_addr_base;
+ u32 *sensor_virt_addr[MAX_HID_DEVICES];
+ phys_addr_t sensor_phys_addr[MAX_HID_DEVICES];
+ u32 sensor_sts[MAX_HID_DEVICES];
+ u32 sensor_requested_cnt[MAX_HID_DEVICES];
+ u8 report_type[MAX_HID_DEVICES];
+ u8 report_id[MAX_HID_DEVICES];
+ u8 sensor_idx[MAX_HID_DEVICES];
+ u8 *feature_report[MAX_HID_DEVICES];
+ u8 *input_report[MAX_HID_DEVICES];
+ u8 request_done[MAX_HID_DEVICES];
+ struct delayed_work work;
+ struct delayed_work work_buffer;
+};
+
+/**
+ * struct amdtp_hid_data - Per instance HID data
+ * @index: Device index in the order of enumeration
+ * @request_done: Get Feature/Input report complete flag
+ * used during get/set request from hid core
+ * @cli_data: Link to the client instance
+ * @hid_wait: Completion waitq
+ *
+ * Used to tie hid->driver data to driver client instance
+ */
+struct amdtp_hid_data {
+ int index;
+ struct amdtp_cl_data *cli_data;
+ wait_queue_head_t hid_wait;
+};
+
+/* Interface functions between HID LL driver and AMD SFH client */
+void hid_amdtp_set_feature(struct hid_device *hid, char *buf, u32 len, int report_id);
+void hid_amdtp_get_report(struct hid_device *hid, int report_id, int report_type);
+int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data);
+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);
+void amd_sfh_set_report(struct hid_device *hid, int report_id, int report_type);
+void amdtp_hid_wakeup(struct hid_device *hid);
+#endif
--
2.25.1