Commit 9d8d5173 authored by Even Xu's avatar Even Xu Committed by Jiri Kosina
Browse files

HID: intel-thc-hid: intel-quickspi: Add HIDSPI protocol implementation

Intel QuickSPI driver uses THC hardware to accelerate HID over SPI
(HIDSPI) protocol flow.

This patch implements all data flows described in HID over SPI protocol
SPEC by using THC hardware layer APIs.

HID over SPI SPEC:
https://www.microsoft.com/download/details.aspx?id=103325



Co-developed-by: default avatarXinpeng Sun <xinpeng.sun@intel.com>
Signed-off-by: default avatarXinpeng Sun <xinpeng.sun@intel.com>
Signed-off-by: default avatarEven Xu <even.xu@intel.com>
Tested-by: default avatarRui Zhang <rui1.zhang@intel.com>
Tested-by: default avatarMark Pearson <mpearson-lenovo@squebb.ca>
Reviewed-by: default avatarSrinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Reviewed-by: default avatarMark Pearson <mpearson-lenovo@squebb.ca>
Tested-by: default avatarAaron Ma <aaron.ma@canonical.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.com>
parent 7cb06f08
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -12,5 +12,6 @@ intel-thc-objs += intel-thc/intel-thc-dma.o
obj-$(CONFIG_INTEL_QUICKSPI) += intel-quickspi.o
intel-quickspi-objs += intel-quickspi/pci-quickspi.o
intel-quickspi-objs += intel-quickspi/quickspi-hid.o
intel-quickspi-objs += intel-quickspi/quickspi-protocol.o

ccflags-y += -I $(src)/intel-thc
+38 −0
Original line number Diff line number Diff line
@@ -4,7 +4,12 @@
#ifndef _QUICKSPI_DEV_H_
#define _QUICKSPI_DEV_H_

#include <linux/bits.h>
#include <linux/hid-over-spi.h>
#include <linux/sizes.h>
#include <linux/wait.h>

#include "quickspi-protocol.h"

#define PCI_DEVICE_ID_INTEL_THC_MTL_DEVICE_ID_SPI_PORT1		0x7E49
#define PCI_DEVICE_ID_INTEL_THC_MTL_DEVICE_ID_SPI_PORT2		0x7E4B
@@ -92,6 +97,21 @@ struct acpi_device;
 * @active_ltr_val: THC active LTR value
 * @low_power_ltr_val: THC low power LTR value
 * @report_descriptor: store a copy of device report descriptor
 * @input_buf: store a copy of latest input report data
 * @report_buf: store a copy of latest input/output report packet from set/get feature
 * @report_len: the length of input/output report packet
 * @reset_ack_wq: workqueue for waiting reset response from device
 * @reset_ack: indicate reset response received or not
 * @nondma_int_received_wq: workqueue for waiting THC non-DMA interrupt
 * @nondma_int_received: indicate THC non-DMA interrupt received or not
 * @report_desc_got_wq: workqueue for waiting device report descriptor
 * @report_desc_got: indicate device report descritor received or not
 * @set_power_on_wq: workqueue for waiting set power on response from device
 * @set_power_on: indicate set power on response received or not
 * @get_feature_cmpl_wq: workqueue for waiting get feature response from device
 * @get_feature_cmpl: indicate get feature received or not
 * @set_feature_cmpl_wq: workqueue for waiting set feature to device
 * @set_feature_cmpl: indicate set feature send complete or not
 */
struct quickspi_device {
	struct device *dev;
@@ -121,6 +141,24 @@ struct quickspi_device {
	u32 low_power_ltr_val;

	u8 *report_descriptor;
	u8 *input_buf;
	u8 *report_buf;
	u32 report_len;

	wait_queue_head_t reset_ack_wq;
	bool reset_ack;

	wait_queue_head_t nondma_int_received_wq;
	bool nondma_int_received;

	wait_queue_head_t report_desc_got_wq;
	bool report_desc_got;

	wait_queue_head_t get_report_cmpl_wq;
	bool get_report_cmpl;

	wait_queue_head_t set_report_cmpl_wq;
	bool set_report_cmpl;
};

#endif /* _QUICKSPI_DEV_H_ */
+16 −1
Original line number Diff line number Diff line
@@ -51,7 +51,22 @@ static int quickspi_hid_raw_request(struct hid_device *hid,
				    __u8 *buf, size_t len,
				    unsigned char rtype, int reqtype)
{
	return 0;
	struct quickspi_device *qsdev = hid->driver_data;
	int ret = 0;

	switch (reqtype) {
	case HID_REQ_GET_REPORT:
		ret = quickspi_get_report(qsdev, rtype, reportnum, buf);
		break;
	case HID_REQ_SET_REPORT:
		ret = quickspi_set_report(qsdev, rtype, reportnum, buf, len);
		break;
	default:
		dev_err_once(qsdev->dev, "Not supported request type %d\n", reqtype);
		break;
	}

	return ret;
}

static int quickspi_hid_power(struct hid_device *hid, int lvl)
+411 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright © 2024 Intel Corporation */

#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/hid.h>

#include "intel-thc-dev.h"
#include "intel-thc-dma.h"

#include "quickspi-dev.h"
#include "quickspi-hid.h"
#include "quickspi-protocol.h"

/* THC uses HW to accelerate HID over SPI protocol, THC_M_PRT_DEV_INT_CAUSE
 * register is used to store message header and body header, below definition
 * let driver retrieve needed data filed easier from THC_M_PRT_DEV_INT_CAUSE
 * register.
 */
#define HIDSPI_IN_REP_BDY_HDR_REP_TYPE     GENMASK(7, 0)

static int write_cmd_to_txdma(struct quickspi_device *qsdev,
			      int report_type, int report_id,
			      u8 *report_buf, const int report_buf_len)
{
	struct output_report *write_buf;
	int write_buf_len;
	int ret;

	write_buf = (struct output_report *)qsdev->report_buf;

	write_buf->output_hdr.report_type = report_type;
	write_buf->output_hdr.content_len = cpu_to_le16(report_buf_len);
	write_buf->output_hdr.content_id = report_id;

	if (report_buf && report_buf_len > 0)
		memcpy(write_buf->content, report_buf, report_buf_len);

	write_buf_len = HIDSPI_OUTPUT_REPORT_SIZE(report_buf_len);

	ret = thc_dma_write(qsdev->thc_hw, write_buf, write_buf_len);
	if (ret)
		dev_err_once(qsdev->dev, "DMA write failed, ret = %d\n", ret);

	return ret;
}

static int quickspi_get_device_descriptor(struct quickspi_device *qsdev)
{
	u8 read_buf[HIDSPI_INPUT_DEVICE_DESCRIPTOR_SIZE];
	struct output_report output_rep;
	u32 input_len, read_len = 0;
	u32 int_cause_val;
	u8 input_rep_type;
	int ret;

	output_rep.output_hdr.report_type = DEVICE_DESCRIPTOR;
	output_rep.output_hdr.content_len = 0;
	output_rep.output_hdr.content_id = 0;

	qsdev->nondma_int_received = false;

	ret = thc_tic_pio_write(qsdev->thc_hw, qsdev->output_report_addr,
				HIDSPI_OUTPUT_REPORT_SIZE(0), (u32 *)&output_rep);
	if (ret) {
		dev_err_once(qsdev->dev,
			     "Write DEVICE_DESCRIPTOR command failed, ret = %d\n", ret);
		return ret;
	}

	ret = wait_event_interruptible_timeout(qsdev->nondma_int_received_wq,
					       qsdev->nondma_int_received,
					       QUICKSPI_ACK_WAIT_TIMEOUT * HZ);
	if (ret <= 0 || !qsdev->nondma_int_received) {
		dev_err_once(qsdev->dev, "Wait DEVICE_DESCRIPTOR timeout, ret:%d\n", ret);
		return -ETIMEDOUT;
	}
	qsdev->nondma_int_received = false;

	int_cause_val = thc_int_cause_read(qsdev->thc_hw);
	input_len = FIELD_GET(HIDSPI_INPUT_HEADER_REPORT_LEN, int_cause_val);

	input_len = input_len * sizeof(u32);
	if (input_len != HIDSPI_INPUT_DEVICE_DESCRIPTOR_SIZE) {
		dev_err_once(qsdev->dev, "Receive wrong DEVICE_DESCRIPTOR length, len = %u\n",
			     input_len);
		return -EINVAL;
	}

	ret = thc_tic_pio_read(qsdev->thc_hw, qsdev->input_report_bdy_addr,
			       input_len, &read_len, (u32 *)read_buf);
	if (ret || read_len != input_len) {
		dev_err_once(qsdev->dev, "Read DEVICE_DESCRIPTOR failed, ret = %d\n", ret);
		dev_err_once(qsdev->dev, "DEVICE_DESCRIPTOR expected len = %u, actual read = %u\n",
			     input_len, read_len);
		return ret;
	}

	input_rep_type = ((struct input_report_body_header *)read_buf)->input_report_type;

	if (input_rep_type == DEVICE_DESCRIPTOR_RESPONSE) {
		memcpy(&qsdev->dev_desc,
		       read_buf + HIDSPI_INPUT_BODY_HEADER_SIZE,
		       HIDSPI_DEVICE_DESCRIPTOR_SIZE);

		return 0;
	}

	dev_err_once(qsdev->dev, "Unexpected intput report type: %d\n", input_rep_type);
	return -EINVAL;
}

int quickspi_get_report_descriptor(struct quickspi_device *qsdev)
{
	int ret;

	ret = write_cmd_to_txdma(qsdev, REPORT_DESCRIPTOR, 0, NULL, 0);
	if (ret) {
		dev_err_once(qsdev->dev,
			     "Write REPORT_DESCRIPTOR command failed, ret = %d\n", ret);
		return ret;
	}

	ret = wait_event_interruptible_timeout(qsdev->report_desc_got_wq,
					       qsdev->report_desc_got,
					       QUICKSPI_ACK_WAIT_TIMEOUT * HZ);
	if (ret <= 0 || !qsdev->report_desc_got) {
		dev_err_once(qsdev->dev, "Wait Report Descriptor timeout, ret:%d\n", ret);
		return -ETIMEDOUT;
	}
	qsdev->report_desc_got = false;

	return 0;
}

int quickspi_set_power(struct quickspi_device *qsdev,
		       enum hidspi_power_state power_state)
{
	u8 cmd_content = power_state;
	int ret;

	ret = write_cmd_to_txdma(qsdev, COMMAND_CONTENT,
				 HIDSPI_SET_POWER_CMD_ID,
				 &cmd_content,
				 sizeof(cmd_content));
	if (ret) {
		dev_err_once(qsdev->dev, "Write SET_POWER command failed, ret = %d\n", ret);
		return ret;
	}

	return 0;
}

void quickspi_handle_input_data(struct quickspi_device *qsdev, u32 buf_len)
{
	struct input_report_body_header *body_hdr;
	struct input_report_body *input_body;
	u8 *input_report;
	u32 input_len;
	int ret = 0;

	input_body = (struct input_report_body *)qsdev->input_buf;
	body_hdr = &input_body->body_hdr;
	input_len = le16_to_cpu(body_hdr->content_len);

	if (HIDSPI_INPUT_BODY_SIZE(input_len) > buf_len) {
		dev_err_once(qsdev->dev, "Wrong input report length: %u",
			     input_len);
		return;
	}

	switch (body_hdr->input_report_type) {
	case REPORT_DESCRIPTOR_RESPONSE:
		if (input_len != le16_to_cpu(qsdev->dev_desc.rep_desc_len)) {
			dev_err_once(qsdev->dev, "Unexpected report descriptor length: %u\n",
				     input_len);
			return;
		}

		memcpy(qsdev->report_descriptor, input_body->content, input_len);

		qsdev->report_desc_got = true;
		wake_up_interruptible(&qsdev->report_desc_got_wq);

		break;

	case COMMAND_RESPONSE:
		if (body_hdr->content_id == HIDSPI_SET_POWER_CMD_ID) {
			dev_dbg(qsdev->dev, "Receive set power on response\n");
		} else {
			dev_err_once(qsdev->dev, "Unknown command response type: %u\n",
				     body_hdr->content_id);
		}

		break;

	case RESET_RESPONSE:
		if (qsdev->state == QUICKSPI_RESETING) {
			qsdev->reset_ack = true;
			wake_up_interruptible(&qsdev->reset_ack_wq);
			dev_dbg(qsdev->dev, "Receive HIR reset response\n");
		} else {
			dev_info(qsdev->dev, "Receive DIR\n");
		}
		break;

	case GET_FEATURE_RESPONSE:
	case GET_INPUT_REPORT_RESPONSE:
		qsdev->report_len = sizeof(body_hdr->content_id) + input_len;
		input_report = input_body->content - sizeof(body_hdr->content_id);

		memcpy(qsdev->report_buf, input_report, qsdev->report_len);

		qsdev->get_report_cmpl = true;
		wake_up_interruptible(&qsdev->get_report_cmpl_wq);

		break;

	case SET_FEATURE_RESPONSE:
	case OUTPUT_REPORT_RESPONSE:
		qsdev->set_report_cmpl = true;
		wake_up_interruptible(&qsdev->set_report_cmpl_wq);

		break;

	case DATA:
		if (input_len > le16_to_cpu(qsdev->dev_desc.max_input_len)) {
			dev_err_once(qsdev->dev, "Unexpected too large input report length: %u\n",
				     input_len);
			return;
		}

		input_len = sizeof(body_hdr->content_id) + input_len;
		input_report = input_body->content - sizeof(body_hdr->content_id);

		ret = quickspi_hid_send_report(qsdev, input_report, input_len);
		if (ret)
			dev_err_once(qsdev->dev, "Failed to send HID input report: %d\n", ret);

		break;

	default:
		dev_err_once(qsdev->dev, "Unsupported input report type: %u\n",
			     body_hdr->input_report_type);
		break;
	}
}

static int acpi_tic_reset(struct quickspi_device *qsdev)
{
	acpi_status status = 0;
	acpi_handle handle;

	if (!qsdev->acpi_dev)
		return -ENODEV;

	handle = acpi_device_handle(qsdev->acpi_dev);
	status = acpi_execute_simple_method(handle, "_RST", 0);
	if (ACPI_FAILURE(status)) {
		dev_err_once(qsdev->dev,
			     "Failed to reset device through ACPI method, ret = %d\n", status);
		return -EIO;
	}

	return 0;
}

int reset_tic(struct quickspi_device *qsdev)
{
	u32 actual_read_len, read_len = 0;
	u32 input_report_len, reset_response, int_cause_val;
	u8  input_rep_type;
	int ret;

	qsdev->state = QUICKSPI_RESETING;

	qsdev->reset_ack = false;

	/* First interrupt uses level trigger to avoid missing interrupt */
	thc_int_trigger_type_select(qsdev->thc_hw, false);

	ret = acpi_tic_reset(qsdev);
	if (ret)
		return ret;

	ret = thc_interrupt_quiesce(qsdev->thc_hw, false);
	if (ret)
		return ret;

	ret = wait_event_interruptible_timeout(qsdev->reset_ack_wq,
					       qsdev->reset_ack,
					       QUICKSPI_ACK_WAIT_TIMEOUT * HZ);
	if (ret <= 0 || !qsdev->reset_ack) {
		dev_err_once(qsdev->dev, "Wait RESET_RESPONSE timeout, ret:%d\n", ret);
		return -ETIMEDOUT;
	}

	int_cause_val = thc_int_cause_read(qsdev->thc_hw);
	input_report_len = FIELD_GET(HIDSPI_INPUT_HEADER_REPORT_LEN, int_cause_val);

	read_len = input_report_len * sizeof(u32);
	if (read_len != HIDSPI_INPUT_BODY_SIZE(0)) {
		dev_err_once(qsdev->dev, "Receive wrong RESET_RESPONSE, len = %u\n",
			     read_len);
		return -EINVAL;
	}

	/* Switch to edge trigger matching with HIDSPI protocol definition */
	thc_int_trigger_type_select(qsdev->thc_hw, true);

	ret = thc_tic_pio_read(qsdev->thc_hw, qsdev->input_report_bdy_addr,
			       read_len, &actual_read_len,
			       (u32 *)&reset_response);
	if (ret || actual_read_len != read_len) {
		dev_err_once(qsdev->dev, "Read RESET_RESPONSE body failed, ret = %d\n", ret);
		dev_err_once(qsdev->dev, "RESET_RESPONSE body expected len = %u, actual = %u\n",
			     read_len, actual_read_len);
		return ret;
	}

	input_rep_type = FIELD_GET(HIDSPI_IN_REP_BDY_HDR_REP_TYPE, reset_response);

	if (input_rep_type == RESET_RESPONSE) {
		dev_dbg(qsdev->dev, "RESET_RESPONSE received\n");
	} else {
		dev_err_once(qsdev->dev,
			     "Unexpected input report type: %d, expect RESET_RESPONSE\n",
			     input_rep_type);
		return -EINVAL;
	}

	qsdev->state = QUICKSPI_RESETED;

	ret = quickspi_get_device_descriptor(qsdev);
	if (ret)
		return ret;

	return 0;
}

int quickspi_get_report(struct quickspi_device *qsdev,
			u8 report_type, unsigned int report_id, void *buf)
{
	int rep_type;
	int ret;

	if (report_type == HID_INPUT_REPORT) {
		rep_type = GET_INPUT_REPORT;
	} else if (report_type == HID_FEATURE_REPORT) {
		rep_type = GET_FEATURE;
	} else {
		dev_err_once(qsdev->dev, "Unsupported report type for GET REPORT: %d\n",
			     report_type);
		return -EINVAL;
	}

	ret = write_cmd_to_txdma(qsdev, rep_type, report_id, NULL, 0);
	if (ret) {
		dev_err_once(qsdev->dev, "Write GET_REPORT command failed, ret = %d\n", ret);
		return ret;
	}

	ret = wait_event_interruptible_timeout(qsdev->get_report_cmpl_wq,
					       qsdev->get_report_cmpl,
					       QUICKSPI_ACK_WAIT_TIMEOUT * HZ);
	if (ret <= 0 || !qsdev->get_report_cmpl) {
		dev_err_once(qsdev->dev, "Wait Get Report Response timeout, ret:%d\n", ret);
		return -ETIMEDOUT;
	}
	qsdev->get_report_cmpl = false;

	memcpy(buf, qsdev->report_buf, qsdev->report_len);

	return qsdev->report_len;
}

int quickspi_set_report(struct quickspi_device *qsdev,
			u8 report_type, unsigned int report_id,
			void *buf, u32 buf_len)
{
	int rep_type;
	int ret;

	if (report_type == HID_OUTPUT_REPORT) {
		rep_type = OUTPUT_REPORT;
	} else if (report_type == HID_FEATURE_REPORT) {
		rep_type = SET_FEATURE;
	} else {
		dev_err_once(qsdev->dev, "Unsupported report type for SET REPORT: %d\n",
			     report_type);
		return -EINVAL;
	}

	ret = write_cmd_to_txdma(qsdev, rep_type, report_id, buf + 1, buf_len - 1);
	if (ret) {
		dev_err_once(qsdev->dev, "Write SET_REPORT command failed, ret = %d\n", ret);
		return ret;
	}

	ret = wait_event_interruptible_timeout(qsdev->set_report_cmpl_wq,
					       qsdev->set_report_cmpl,
					       QUICKSPI_ACK_WAIT_TIMEOUT * HZ);
	if (ret <= 0 || !qsdev->set_report_cmpl) {
		dev_err_once(qsdev->dev, "Wait Set Report Response timeout, ret:%d\n", ret);
		return -ETIMEDOUT;
	}
	qsdev->set_report_cmpl = false;

	return buf_len;
}
+25 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2024 Intel Corporation */

#ifndef _QUICKSPI_PROTOCOL_H_
#define _QUICKSPI_PROTOCOL_H_

#include <linux/hid-over-spi.h>

#define QUICKSPI_ACK_WAIT_TIMEOUT    5

struct quickspi_device;

void quickspi_handle_input_data(struct quickspi_device *qsdev, u32 buf_len);
int quickspi_get_report(struct quickspi_device *qsdev, u8 report_type,
			unsigned int report_id, void *buf);
int quickspi_set_report(struct quickspi_device *qsdev, u8 report_type,
			unsigned int report_id, void *buf, u32 buf_len);
int quickspi_get_report_descriptor(struct quickspi_device *qsdev);

int quickspi_set_power(struct quickspi_device *qsdev,
		       enum hidspi_power_state power_state);

int reset_tic(struct quickspi_device *qsdev);

#endif /* _QUICKSPI_PROTOCOL_H_ */
Loading