2022-07-07 17:00:53

by Jarrett Schultz

[permalink] [raw]
Subject: [PATCH v5 0/6] Add spi-hid, transport for HID over SPI bus

From: Jarrett Schultz <[email protected]>

Surface Duo devices use a touch digitizer that communicates to the main
SoC via SPI and presents itself as a HID device. This patch's goal is to
add the spi-hid transport driver to drivers/hid. The driver follows the
publically available HID Over SPI Protocol Specification version 1.0.

The specification is available at
https://www.microsoft.com/en-us/download/details.aspx?id=103325.

In the initial commits there are some HID core changes to support a SPI
device, a change to HID documentation, HID over SPI Device Tree
bindings, and finally the SPI HID transport driver.

Dmitry Antipov (5):
HID: Add BUS_SPI support when printing out device info in
hid_connect()
HID: define HID_SPI_DEVICE macro in hid.h
dt-bindings: input: Document Microsoft G6 Touch Digitizer
Documentation: Correction in HID output_report callback description.
HID: add spi-hid, transport driver for HID over SPI bus

Jarrett Schultz (1):
Enable building drivers/hid/spi-hid as a module

.../input/microsoft,g6-touch-digitizer.yaml | 135 ++
Documentation/hid/hid-transport.rst | 4 +-
arch/arm64/configs/defconfig | 1 +
drivers/hid/Kconfig | 2 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-core.c | 3 +
drivers/hid/spi-hid/Kconfig | 25 +
drivers/hid/spi-hid/Makefile | 12 +
drivers/hid/spi-hid/spi-hid-core.c | 1326 +++++++++++++++++
drivers/hid/spi-hid/spi-hid-core.h | 188 +++
drivers/hid/spi-hid/spi-hid-of.c | 141 ++
drivers/hid/spi-hid/spi-hid-of.h | 30 +
drivers/hid/spi-hid/spi-hid_trace.h | 194 +++
drivers/hid/spi-hid/trace.c | 9 +
include/linux/hid.h | 2 +
15 files changed, 2071 insertions(+), 2 deletions(-)
create mode 100644 Documentation/devicetree/bindings/input/microsoft,g6-touch-digitizer.yaml
create mode 100644 drivers/hid/spi-hid/Kconfig
create mode 100644 drivers/hid/spi-hid/Makefile
create mode 100644 drivers/hid/spi-hid/spi-hid-core.c
create mode 100644 drivers/hid/spi-hid/spi-hid-core.h
create mode 100644 drivers/hid/spi-hid/spi-hid-of.c
create mode 100644 drivers/hid/spi-hid/spi-hid-of.h
create mode 100644 drivers/hid/spi-hid/spi-hid_trace.h
create mode 100644 drivers/hid/spi-hid/trace.c

--
2.25.1


2022-07-07 17:01:11

by Jarrett Schultz

[permalink] [raw]
Subject: [PATCH v5 3/6] dt-bindings: input: Document Microsoft G6 Touch Digitizer

From: Jarrett Schultz <[email protected]>

Documentation describes the required and optional properties for
implementing Device Tree for a Microsoft G6 Touch Digitizer that
supports HID over SPI Protocol 1.0 specification.

Signed-off-by: Dmitry Antipov <[email protected]>
Signed-off-by: Jarrett Schultz <[email protected]>
---
.../input/microsoft,g6-touch-digitizer.yaml | 135 ++++++++++++++++++
1 file changed, 135 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/microsoft,g6-touch-digitizer.yaml

diff --git a/Documentation/devicetree/bindings/input/microsoft,g6-touch-digitizer.yaml b/Documentation/devicetree/bindings/input/microsoft,g6-touch-digitizer.yaml
new file mode 100644
index 000000000000..b607bbb32a42
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/microsoft,g6-touch-digitizer.yaml
@@ -0,0 +1,135 @@
+# SPDX-License-Identifier: (GPL-2.0-only or BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/microsoft,g6-touch-digitizer.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microsoft G6 Touch Digitizer
+
+maintainers:
+ - Dmitry Antipov <[email protected]>
+
+description: |
+ Microsoft G6 touch digitizer is a HID over SPI device supporting HID Over SPI
+ Protocol Specification 1.0, available at
+ https://www.microsoft.com/en-us/download/details.aspx?id=103325.
+
+properties:
+ compatible:
+ items:
+ - const: microsoft,g6-touch-digitizer
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+ description:
+ GPIO specifier for the digitizer's reset pin (active low). The line must
+ be flagged with GPIO_ACTIVE_LOW.
+
+ vdd-supply:
+ description:
+ Regulator for the VDD supply voltage.
+
+ input-report-header-address:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 0
+ maximum: 16777215
+ description:
+ A value to be included in the Read Approval packet, listing an address of
+ the input report header to be put on the SPI bus. This address has 24
+ bits.
+
+ input-report-body-address:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 0
+ maximum: 16777215
+ description:
+ A value to be included in the Read Approval packet, listing an address of
+ the input report body to be put on the SPI bus. This address has 24 bits.
+
+ output-report-address:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 0
+ maximum: 16777215
+ description:
+ A value to be included in the Output Report sent by the host, listing an
+ address where the output report on the SPI bus is to be written to. This
+ address has 24 bits.
+
+ post-power-on-delay-ms:
+ description:
+ Optional time in ms required by the device after enabling its regulators
+ or powering it on, before it is ready for communication.
+
+ minimal-reset-delay-ms:
+ description:
+ Optional minimum amount of time in ms that device needs to be in reset
+ state for the reset to take effect.
+
+ read-opcode:
+ description:
+ Value to be used in Read Approval packets. 1 byte.
+
+ write-opcode:
+ description:
+ Value to be used in Write Approval packets. 1 byte.
+
+ hid-over-spi-flags:
+ description:
+ 16 bits.
+ Bits 0-12 - Reserved (must be 0)
+ Bit 13 - SPI Write Mode. Possible values -
+ * 0b0- Writes are carried out in Single-SPI mode
+ * 0b1- Writes are carried out in the Multi-SPI mode specified by bits
+ 14-15
+ Bits 14-15 - Multi-SPI Mode. Possible values -
+ * 0b00- Single SPI
+ * 0b01- Dual SPI
+ * 0b10- Quad SPI
+
+required:
+ - compatible
+ - interrupts
+ - reset-gpios
+ - vdd-supply
+ - input-report-header-address
+ - input-report-body-address
+ - output-report-address
+ - read-opcode
+ - write-opcode
+ - hid-over-spi-flags
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/gpio/gpio.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ hid@0 {
+ compatible = "microsoft,g6-touch-digitizer";
+ reg = <0x0>;
+ interrupts-extended = <&gpio 42 IRQ_TYPE_EDGE_FALLING>;
+ reset-gpios = <&gpio 27 GPIO_ACTIVE_LOW>;
+ vdd-supply = <&pm8350c_l3>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&ts_d6_reset_assert &ts_d6_int_bias>;
+ input-report-header-address = <0x1000>;
+ input-report-body-address = <0x1004>;
+ output-report-address = <0x2000>;
+ read-opcode = <0x0b>;
+ write-opcode = <0x02>;
+ hid-over-spi-flags = <0x00>;
+ post-power-on-delay-ms = <5>;
+ minimal-reset-delay-ms = <5>;
+ };
+ };
--
2.25.1

2022-07-07 17:01:14

by Jarrett Schultz

[permalink] [raw]
Subject: [PATCH v5 6/6] Enable building drivers/hid/spi-hid as a module

From: Jarrett Schultz <[email protected]>

Signed-off-by: Jarrett Schultz <[email protected]>
---
arch/arm64/configs/defconfig | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 7d1105343bc2..731fc6e67f31 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -848,6 +848,7 @@ CONFIG_SND_AUDIO_GRAPH_CARD2=m
CONFIG_HID_MULTITOUCH=m
CONFIG_I2C_HID_ACPI=m
CONFIG_I2C_HID_OF=m
+CONFIG_SPI_HID=m
CONFIG_USB=y
CONFIG_USB_OTG=y
CONFIG_USB_XHCI_HCD=y
--
2.25.1

2022-07-07 17:01:29

by Jarrett Schultz

[permalink] [raw]
Subject: [PATCH v5 5/6] HID: add spi-hid, transport driver for HID over SPI bus

From: Jarrett Schultz <[email protected]>

This driver follows HID Over SPI Protocol Specification 1.0 available at
https://www.microsoft.com/en-us/download/details.aspx?id=103325. The
initial version of the driver does not support: 1) multi-fragment input
reports, 2) sending GET_INPUT and COMMAND output report types and
processing their respective acknowledge input reports, and 3) device
sleep power state.

Signed-off-by: Dmitry Antipov <[email protected]>
---
drivers/hid/Kconfig | 2 +
drivers/hid/Makefile | 1 +
drivers/hid/spi-hid/Kconfig | 25 +
drivers/hid/spi-hid/Makefile | 12 +
drivers/hid/spi-hid/spi-hid-core.c | 1326 +++++++++++++++++++++++++++
drivers/hid/spi-hid/spi-hid-core.h | 188 ++++
drivers/hid/spi-hid/spi-hid-of.c | 141 +++
drivers/hid/spi-hid/spi-hid-of.h | 30 +
drivers/hid/spi-hid/spi-hid_trace.h | 194 ++++
drivers/hid/spi-hid/trace.c | 9 +
10 files changed, 1928 insertions(+)
create mode 100644 drivers/hid/spi-hid/Kconfig
create mode 100644 drivers/hid/spi-hid/Makefile
create mode 100644 drivers/hid/spi-hid/spi-hid-core.c
create mode 100644 drivers/hid/spi-hid/spi-hid-core.h
create mode 100644 drivers/hid/spi-hid/spi-hid-of.c
create mode 100644 drivers/hid/spi-hid/spi-hid-of.h
create mode 100644 drivers/hid/spi-hid/spi-hid_trace.h
create mode 100644 drivers/hid/spi-hid/trace.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 70da5931082f..a396829c4c0b 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1314,6 +1314,8 @@ source "drivers/hid/usbhid/Kconfig"

source "drivers/hid/i2c-hid/Kconfig"

+source "drivers/hid/spi-hid/Kconfig"
+
source "drivers/hid/intel-ish-hid/Kconfig"

source "drivers/hid/amd-sfh-hid/Kconfig"
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index cac2cbe26d11..1699863e4021 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -149,6 +149,7 @@ obj-$(CONFIG_USB_MOUSE) += usbhid/
obj-$(CONFIG_USB_KBD) += usbhid/

obj-$(CONFIG_I2C_HID_CORE) += i2c-hid/
+obj-$(CONFIG_SPI_HID) += spi-hid/

obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/
obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig
new file mode 100644
index 000000000000..37302d658162
--- /dev/null
+++ b/drivers/hid/spi-hid/Kconfig
@@ -0,0 +1,25 @@
+#
+# Copyright (c) 2021 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+#
+menu "SPI HID support"
+ depends on SPI
+
+config SPI_HID
+ tristate "HID over SPI transport layer"
+ default n
+ depends on SPI && INPUT && OF
+ select HID
+ help
+ Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
+ other HID based devices which is connected to your computer via SPI.
+
+ If unsure, say N.
+
+ This support is also available as a module. If so, the module
+ will be called spi-hid.
+
+endmenu
diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile
new file mode 100644
index 000000000000..b9f73982b956
--- /dev/null
+++ b/drivers/hid/spi-hid/Makefile
@@ -0,0 +1,12 @@
+#
+# Copyright (c) 2021 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+#
+# Makefile for the HID over SPI transport driver
+#
+CFLAGS_trace.o = -I$(src)
+obj-$(CONFIG_SPI_HID) += spi-hid.o
+spi-hid-objs := spi-hid-core.o spi-hid-of.o trace.o
diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-hid-core.c
new file mode 100644
index 000000000000..8a29bfe40590
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-core.c
@@ -0,0 +1,1326 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HID over SPI protocol implementation
+ *
+ * Copyright (c) 2021 Microsoft Corporation
+ *
+ * This code is partly based on "HID over I2C protocol implementation:
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <[email protected]>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ *
+ * which in turn is partly based on "USB HID support for Linux":
+ *
+ * Copyright (c) 1999 Andreas Gal
+ * Copyright (c) 2000-2005 Vojtech Pavlik <[email protected]>
+ * Copyright (c) 2005 Michael Haboustak <[email protected]> for Concept2, Inc
+ * Copyright (c) 2007-2008 Oliver Neukum
+ * Copyright (c) 2006-2010 Jiri Kosina
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/string.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include "spi-hid-core.h"
+#include "spi-hid_trace.h"
+#include "spi-hid-of.h"
+#include "../hid-ids.h"
+
+#define SPI_HID_MAX_RESET_ATTEMPTS 3
+
+static struct hid_ll_driver spi_hid_ll_driver;
+
+static void spi_hid_populate_read_approvals(struct spi_hid_of_config *conf,
+ u8 *header_buf, u8 *body_buf)
+{
+ header_buf[0] = conf->read_opcode;
+ header_buf[1] = (conf->input_report_header_address >> 16) & 0xff;
+ header_buf[2] = (conf->input_report_header_address >> 8) & 0xff;
+ header_buf[3] = (conf->input_report_header_address >> 0) & 0xff;
+ header_buf[4] = SPI_HID_READ_APPROVAL_CONSTANT;
+
+ body_buf[0] = conf->read_opcode;
+ body_buf[1] = (conf->input_report_body_address >> 16) & 0xff;
+ body_buf[2] = (conf->input_report_body_address >> 8) & 0xff;
+ body_buf[3] = (conf->input_report_body_address >> 0) & 0xff;
+ body_buf[4] = SPI_HID_READ_APPROVAL_CONSTANT;
+}
+
+static void spi_hid_parse_dev_desc(struct spi_hid_device_desc_raw *raw,
+ struct spi_hid_device_descriptor *desc)
+{
+ desc->hid_version = le16_to_cpu(raw->bcdVersion);
+ desc->report_descriptor_length = le16_to_cpu(raw->wReportDescLength);
+ desc->max_input_length = le16_to_cpu(raw->wMaxInputLength);
+ desc->max_output_length = le16_to_cpu(raw->wMaxOutputLength);
+
+ /* FIXME: multi-fragment not supported, field below not used */
+ desc->max_fragment_length = le16_to_cpu(raw->wMaxFragmentLength);
+
+ desc->vendor_id = le16_to_cpu(raw->wVendorID);
+ desc->product_id = le16_to_cpu(raw->wProductID);
+ desc->version_id = le16_to_cpu(raw->wVersionID);
+ desc->no_output_report_ack = le16_to_cpu(raw->wFlags) & BIT(0);
+}
+
+static void spi_hid_populate_input_header(u8 *buf,
+ struct spi_hid_input_header *header)
+{
+ header->version = buf[0] & 0xf;
+ header->report_length = le16_to_cpu((buf[1] | ((buf[2] & 0x3f) << 8)) * 4);
+ header->last_fragment_flag = (buf[2] & 0x40) >> 6;
+ header->sync_const = buf[3];
+}
+
+static void spi_hid_populate_input_body(u8 *buf,
+ struct spi_hid_input_body *body)
+{
+ body->report_type = buf[0];
+ body->content_length = le16_to_cpu(buf[1] | (buf[2] << 8));
+ body->content_id = buf[3];
+}
+
+static void spi_hid_input_report_prepare(struct spi_hid_input_buf *buf,
+ struct spi_hid_input_report *report)
+{
+ struct spi_hid_input_header header;
+ struct spi_hid_input_body body;
+
+ spi_hid_populate_input_header(buf->header, &header);
+ spi_hid_populate_input_body(buf->body, &body);
+ report->report_type = body.report_type;
+ report->content_length = body.content_length;
+ report->content_id = body.content_id;
+ report->content = buf->content;
+}
+
+static void spi_hid_populate_output_header(u8 *buf,
+ struct spi_hid_of_config *conf,
+ struct spi_hid_output_report *report)
+{
+ buf[0] = conf->write_opcode;
+ buf[1] = (conf->output_report_address >> 16) & 0xff;
+ buf[2] = (conf->output_report_address >> 8) & 0xff;
+ buf[3] = (conf->output_report_address >> 0) & 0xff;
+ buf[4] = report->report_type;
+ buf[5] = report->content_length & 0xff;
+ buf[6] = (report->content_length >> 8) & 0xff;
+ buf[7] = report->content_id;
+}
+
+static int spi_hid_input_sync(struct spi_hid *shid, void *buf, u16 length,
+ bool is_header)
+{
+ int ret;
+ struct device *dev = &shid->spi->dev;
+
+ shid->input_transfer[0].tx_buf = is_header ? shid->read_approval_header :
+ shid->read_approval_body;
+ shid->input_transfer[0].len = SPI_HID_READ_APPROVAL_LEN;
+
+ shid->input_transfer[1].rx_buf = buf;
+ shid->input_transfer[1].len = length;
+
+ spi_message_init_with_transfers(&shid->input_message,
+ shid->input_transfer, 2);
+
+ trace_spi_hid_input_sync(shid,
+ shid->input_transfer[0].tx_buf,
+ shid->input_transfer[0].len,
+ shid->input_transfer[1].rx_buf,
+ shid->input_transfer[1].len, 0);
+
+ ret = spi_sync(shid->spi, &shid->input_message);
+ if (ret) {
+ dev_err(dev, "Error starting async transfer: %d, resetting\n",
+ ret);
+ shid->bus_error_count++;
+ shid->bus_last_error = ret;
+ schedule_work(&shid->error_work);
+ }
+
+ return ret;
+}
+
+static int spi_hid_output(struct spi_hid *shid, void *buf, u16 length)
+{
+ struct spi_transfer transfer;
+ struct spi_message message;
+ int ret;
+
+ memset(&transfer, 0, sizeof(transfer));
+
+ transfer.tx_buf = buf;
+ transfer.len = length;
+
+ spi_message_init_with_transfers(&message, &transfer, 1);
+
+ trace_spi_hid_output_begin(shid, transfer.tx_buf,
+ transfer.len, NULL, 0, 0);
+
+ ret = spi_sync(shid->spi, &message);
+
+ trace_spi_hid_output_end(shid, transfer.tx_buf,
+ transfer.len, NULL, 0, ret);
+
+ if (ret) {
+ shid->bus_error_count++;
+ shid->bus_last_error = ret;
+ }
+
+ return ret;
+}
+
+static const char *const spi_hid_power_mode_string(u8 power_state)
+{
+ switch (power_state) {
+ case SPI_HID_POWER_MODE_ON:
+ return "d0";
+ case SPI_HID_POWER_MODE_SLEEP:
+ return "d2";
+ case SPI_HID_POWER_MODE_OFF:
+ return "d3";
+ case SPI_HID_POWER_MODE_WAKING_SLEEP:
+ return "d3*";
+ default:
+ return "unknown";
+ }
+}
+
+static void spi_hid_suspend(struct spi_hid *shid)
+{
+ int ret;
+ struct device *dev = &shid->spi->dev;
+
+ if (shid->power_state == SPI_HID_POWER_MODE_OFF)
+ return;
+
+ if (shid->irq_enabled) {
+ disable_irq(shid->spi->irq);
+ shid->irq_enabled = false;
+ } else {
+ dev_err(dev, "%s called with interrupt already disabled\n",
+ __func__);
+ }
+
+ shid->ready = false;
+
+ spi_hid_of_assert_reset(&shid->conf);
+
+ ret = spi_hid_of_power_down(&shid->conf);
+ if (ret) {
+ dev_err(dev, "%s: could not power down\n", __func__);
+ shid->regulator_error_count++;
+ shid->regulator_last_error = ret;
+ return;
+ }
+
+ shid->power_state = SPI_HID_POWER_MODE_OFF;
+}
+
+static void spi_hid_resume(struct spi_hid *shid)
+{
+ int ret;
+ struct device *dev = &shid->spi->dev;
+
+ if (shid->power_state == SPI_HID_POWER_MODE_ON)
+ return;
+
+ if (!shid->irq_enabled) {
+ enable_irq(shid->spi->irq);
+ shid->irq_enabled = true;
+ } else {
+ dev_err(dev, "%s called with interrupt already enabled\n",
+ __func__);
+ }
+
+ ret = spi_hid_of_power_up(&shid->conf);
+ if (ret) {
+ dev_err(dev, "%s: could not power up\n", __func__);
+ shid->regulator_error_count++;
+ shid->regulator_last_error = ret;
+ return;
+ }
+ shid->power_state = SPI_HID_POWER_MODE_ON;
+
+ spi_hid_of_deassert_reset(&shid->conf);
+}
+
+static struct hid_device *spi_hid_disconnect_hid(struct spi_hid *shid)
+{
+ struct hid_device *hid = shid->hid;
+
+ shid->hid = NULL;
+
+ return hid;
+}
+
+static void spi_hid_stop_hid(struct spi_hid *shid)
+{
+ struct hid_device *hid;
+
+ hid = spi_hid_disconnect_hid(shid);
+ if (hid) {
+ cancel_work_sync(&shid->create_device_work);
+ cancel_work_sync(&shid->refresh_device_work);
+ hid_destroy_device(hid);
+ }
+}
+
+static void spi_hid_error_work(struct work_struct *work)
+{
+ struct spi_hid *shid = container_of(work, struct spi_hid, error_work);
+ struct device *dev = &shid->spi->dev;
+ int ret;
+
+ mutex_lock(&shid->power_lock);
+ if (shid->power_state == SPI_HID_POWER_MODE_OFF)
+ goto out;
+
+ if (shid->attempts++ >= SPI_HID_MAX_RESET_ATTEMPTS) {
+ dev_err(dev, "unresponsive device, aborting.\n");
+ spi_hid_stop_hid(shid);
+ spi_hid_of_assert_reset(&shid->conf);
+ ret = spi_hid_of_power_down(&shid->conf);
+ if (ret) {
+ dev_err(dev, "failed to disable regulator\n");
+ shid->regulator_error_count++;
+ shid->regulator_last_error = ret;
+ }
+ goto out;
+ }
+
+ trace_spi_hid_error_work(shid);
+
+ shid->ready = false;
+
+ spi_hid_of_assert_reset(&shid->conf);
+
+ shid->power_state = SPI_HID_POWER_MODE_OFF;
+ cancel_work_sync(&shid->reset_response_work);
+
+ spi_hid_of_sleep_minimal_reset_delay(&shid->conf);
+
+ shid->power_state = SPI_HID_POWER_MODE_ON;
+
+ spi_hid_of_deassert_reset(&shid->conf);
+out:
+ mutex_unlock(&shid->power_lock);
+}
+
+static int spi_hid_send_output_report(struct spi_hid *shid,
+ struct spi_hid_output_report *report)
+{
+ struct spi_hid_output_buf *buf = &shid->output;
+ struct device *dev = &shid->spi->dev;
+ u16 report_length;
+ u16 padded_length;
+ u8 padding;
+ int ret;
+
+ if (report->content_length > shid->desc.max_output_length) {
+ dev_err(dev, "Output report too big, content_length 0x%x\n",
+ report->content_length);
+ ret = -E2BIG;
+ goto out;
+ }
+
+ spi_hid_populate_output_header(buf->header, &shid->conf, report);
+
+ if (report->content_length)
+ memcpy(&buf->content, report->content, report->content_length);
+
+ report_length = sizeof(buf->header) + report->content_length;
+ padded_length = round_up(report_length, 4);
+ padding = padded_length - report_length;
+ memset(&buf->content[report->content_length], 0, padding);
+
+ ret = spi_hid_output(shid, buf, padded_length);
+ if (ret) {
+ dev_err(dev, "Failed output transfer\n");
+ goto out;
+ }
+
+ return 0;
+
+out:
+ return ret;
+}
+
+static int spi_hid_sync_request(struct spi_hid *shid,
+ struct spi_hid_output_report *report)
+{
+ struct device *dev = &shid->spi->dev;
+ int ret = 0;
+
+ ret = spi_hid_send_output_report(shid, report);
+ if (ret) {
+ dev_err(dev, "Failed to transfer output report\n");
+ return ret;
+ }
+
+ mutex_unlock(&shid->output_lock);
+ ret = wait_for_completion_interruptible_timeout(&shid->output_done,
+ msecs_to_jiffies(1000));
+ mutex_lock(&shid->output_lock);
+ if (ret == 0) {
+ dev_err(dev, "Response timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+/**
+ * Handle the reset response from the FW by sending a request for the device
+ * descriptor.
+ */
+static void spi_hid_reset_response_work(struct work_struct *work)
+{
+ struct spi_hid *shid =
+ container_of(work, struct spi_hid, reset_response_work);
+ struct device *dev = &shid->spi->dev;
+ struct spi_hid_output_report report = {
+ .report_type = SPI_HID_OUTPUT_REPORT_TYPE_DEVICE_DESC_REQUEST,
+ .content_length = 0x0,
+ .content_id = SPI_HID_OUTPUT_REPORT_CONTENT_ID_DESC_REQUEST,
+ .content = NULL,
+ };
+ int ret;
+
+ trace_spi_hid_reset_response_work(shid);
+
+ if (shid->ready) {
+ dev_err(dev, "Spontaneous FW reset!");
+ shid->ready = false;
+ shid->dir_count++;
+ }
+
+ if (shid->power_state == SPI_HID_POWER_MODE_OFF)
+ return;
+
+ if (flush_work(&shid->create_device_work))
+ dev_err(dev, "Reset handler waited for create_device_work");
+
+ if (flush_work(&shid->refresh_device_work))
+ dev_err(dev, "Reset handler waited for refresh_device_work");
+
+ mutex_lock(&shid->output_lock);
+ ret = spi_hid_sync_request(shid, &report);
+ mutex_unlock(&shid->output_lock);
+ if (ret) {
+ dev_WARN_ONCE(dev, true,
+ "Failed to send device descriptor request\n");
+ schedule_work(&shid->error_work);
+ }
+}
+
+static int spi_hid_input_report_handler(struct spi_hid *shid,
+ struct spi_hid_input_buf *buf)
+{
+ struct device *dev = &shid->spi->dev;
+ struct spi_hid_input_report r;
+ int ret;
+
+ trace_spi_hid_input_report_handler(shid);
+
+ if (!shid->ready || shid->refresh_in_progress || !shid->hid)
+ return 0;
+
+ spi_hid_input_report_prepare(buf, &r);
+
+ ret = hid_input_report(shid->hid, HID_INPUT_REPORT,
+ r.content - 1,
+ r.content_length + 1, 1);
+
+ if (ret == -ENODEV || ret == -EBUSY) {
+ dev_err(dev, "ignoring report --> %d\n", ret);
+ return 0;
+ } else if (ret) {
+ dev_err(dev, "Bad input report, error %d\n", ret);
+ }
+
+ return ret;
+}
+
+static void spi_hid_response_handler(struct spi_hid *shid,
+ struct spi_hid_input_buf *buf)
+{
+ trace_spi_hid_response_handler(shid);
+
+ /* completion_done returns 0 if there are waiters, otherwise 1 */
+ if (completion_done(&shid->output_done)) {
+ dev_err(&shid->spi->dev, "Unexpected response report\n");
+ } else {
+ if (shid->input.body[0] ==
+ SPI_HID_INPUT_REPORT_TYPE_REPORT_DESC ||
+ shid->input.body[0] ==
+ SPI_HID_INPUT_REPORT_TYPE_GET_FEATURE_RESP) {
+ size_t response_length = (shid->input.body[1] |
+ (shid->input.body[2] << 8)) +
+ sizeof(shid->input.body);
+ memcpy(shid->response.body, shid->input.body,
+ response_length);
+ }
+ complete(&shid->output_done);
+ }
+}
+
+/*
+ * This function returns the length of the report descriptor, or a negative
+ * error code if something went wrong.
+ */
+static int spi_hid_report_descriptor_request(struct spi_hid *shid)
+{
+ int ret;
+ struct device *dev = &shid->spi->dev;
+ struct spi_hid_output_report report = {
+ .report_type = SPI_HID_OUTPUT_REPORT_TYPE_REPORT_DESC_REQUEST,
+ .content_length = 0,
+ .content_id = SPI_HID_OUTPUT_REPORT_CONTENT_ID_DESC_REQUEST,
+ .content = NULL,
+ };
+
+ ret = spi_hid_sync_request(shid, &report);
+ if (ret) {
+ dev_err(dev,
+ "Expected report descriptor not received! Error %d\n",
+ ret);
+ schedule_work(&shid->error_work);
+ goto out;
+ }
+
+ ret = (shid->response.body[1] | (shid->response.body[2] << 8));
+ if (ret != shid->desc.report_descriptor_length) {
+ ret = min_t(unsigned int, ret,
+ shid->desc.report_descriptor_length);
+ dev_err(dev,
+ "Received report descriptor length doesn't match device descriptor field, using min of the two: %d\n",
+ ret);
+ }
+out:
+ return ret;
+}
+
+static void spi_hid_process_input_report(struct spi_hid *shid,
+ struct spi_hid_input_buf *buf)
+{
+ struct spi_hid_input_header header;
+ struct spi_hid_input_body body;
+ struct device *dev = &shid->spi->dev;
+ struct spi_hid_device_desc_raw *raw;
+ int ret;
+
+ trace_spi_hid_process_input_report(shid);
+
+ spi_hid_populate_input_header(buf->header, &header);
+ spi_hid_populate_input_body(buf->body, &body);
+
+ if (body.content_length > header.report_length) {
+ dev_err(dev, "Bad body length %d > %d\n", body.content_length,
+ header.report_length);
+ schedule_work(&shid->error_work);
+ return;
+ }
+
+ switch (body.report_type) {
+ case SPI_HID_INPUT_REPORT_TYPE_DATA:
+ ret = spi_hid_input_report_handler(shid, buf);
+ if (ret)
+ schedule_work(&shid->error_work);
+ break;
+ case SPI_HID_INPUT_REPORT_TYPE_RESET_RESP:
+ schedule_work(&shid->reset_response_work);
+ break;
+ case SPI_HID_INPUT_REPORT_TYPE_DEVICE_DESC:
+ /* Mark the completion done to avoid timeout */
+ spi_hid_response_handler(shid, buf);
+
+ /* Reset attempts at every device descriptor fetch */
+ shid->attempts = 0;
+
+ raw = (struct spi_hid_device_desc_raw *)buf->content;
+
+ /* Validate device descriptor length before parsing */
+ if (body.content_length != SPI_HID_DEVICE_DESCRIPTOR_LENGTH) {
+ dev_err(dev,
+ "Invalid content length %d, expected %d\n",
+ body.content_length,
+ SPI_HID_DEVICE_DESCRIPTOR_LENGTH);
+ schedule_work(&shid->error_work);
+ break;
+ }
+
+ if (le16_to_cpu(raw->wDeviceDescLength) !=
+ SPI_HID_DEVICE_DESCRIPTOR_LENGTH) {
+ dev_err(dev,
+ "Invalid wDeviceDescLength %d, expected %d\n",
+ raw->wDeviceDescLength,
+ SPI_HID_DEVICE_DESCRIPTOR_LENGTH);
+ schedule_work(&shid->error_work);
+ break;
+ }
+
+ spi_hid_parse_dev_desc(raw, &shid->desc);
+
+ if (shid->desc.hid_version != SPI_HID_SUPPORTED_VERSION) {
+ dev_err(dev,
+ "Unsupported device descriptor version %4x\n",
+ shid->desc.hid_version);
+ schedule_work(&shid->error_work);
+ break;
+ }
+
+ if (!shid->hid)
+ schedule_work(&shid->create_device_work);
+ else
+ schedule_work(&shid->refresh_device_work);
+
+ break;
+ case SPI_HID_INPUT_REPORT_TYPE_SET_OUTPUT_REPORT_RESP:
+ if (shid->desc.no_output_report_ack) {
+ dev_err(dev, "Unexpected output report response\n");
+ break;
+ }
+ fallthrough;
+ case SPI_HID_INPUT_REPORT_TYPE_GET_FEATURE_RESP:
+ case SPI_HID_INPUT_REPORT_TYPE_SET_FEATURE_RESP:
+ if (!shid->ready) {
+ dev_err(dev,
+ "Unexpected response report while not ready: 0x%x\n",
+ body.report_type);
+ break;
+ }
+ fallthrough;
+ case SPI_HID_INPUT_REPORT_TYPE_REPORT_DESC:
+ spi_hid_response_handler(shid, buf);
+ break;
+ /*
+ * FIXME: sending GET_INPUT and COMMAND reports not supported, thus
+ * throw away responses to those, they should never come.
+ */
+ case SPI_HID_INPUT_REPORT_TYPE_GET_INPUT_REPORT_RESP:
+ case SPI_HID_INPUT_REPORT_TYPE_COMMAND_RESP:
+ dev_err(dev, "Not a supported report type: 0x%x\n",
+ body.report_type);
+ break;
+ default:
+ dev_err(dev, "Unknown input report: 0x%x\n",
+ body.report_type);
+ schedule_work(&shid->error_work);
+ break;
+ }
+}
+
+static int spi_hid_bus_validate_header(struct spi_hid *shid,
+ struct spi_hid_input_header *header)
+{
+ struct device *dev = &shid->spi->dev;
+
+ if (header->version != SPI_HID_INPUT_HEADER_VERSION) {
+ dev_err(dev, "Unknown input report version (v 0x%x)\n",
+ header->version);
+ return -EINVAL;
+ }
+
+ if (shid->desc.max_input_length != 0 &&
+ header->report_length > shid->desc.max_input_length) {
+ dev_err(dev, "Input report body size %u > max expected of %u\n",
+ header->report_length,
+ shid->desc.max_input_length);
+ return -EMSGSIZE;
+ }
+
+ if (header->last_fragment_flag != 1) {
+ dev_err(dev, "Multi-fragment reports not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (header->sync_const != SPI_HID_INPUT_HEADER_SYNC_BYTE) {
+ dev_err(dev, "Invalid input report sync constant (0x%x)\n",
+ header->sync_const);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int spi_hid_create_device(struct spi_hid *shid)
+{
+ struct hid_device *hid;
+ struct device *dev = &shid->spi->dev;
+ int ret;
+
+ hid = hid_allocate_device();
+
+ if (IS_ERR(hid)) {
+ dev_err(dev, "Failed to allocate hid device: %ld\n",
+ PTR_ERR(hid));
+ ret = PTR_ERR(hid);
+ return ret;
+ }
+
+ hid->driver_data = shid->spi;
+ hid->ll_driver = &spi_hid_ll_driver;
+ hid->dev.parent = &shid->spi->dev;
+ hid->bus = BUS_SPI;
+ hid->version = shid->desc.hid_version;
+ hid->vendor = shid->desc.vendor_id;
+ hid->product = shid->desc.product_id;
+
+ snprintf(hid->name, sizeof(hid->name), "spi %04hX:%04hX",
+ hid->vendor, hid->product);
+ strscpy(hid->phys, dev_name(&shid->spi->dev), sizeof(hid->phys));
+
+ shid->hid = hid;
+
+ ret = hid_add_device(hid);
+ if (ret) {
+ dev_err(dev, "Failed to add hid device: %d\n", ret);
+ /*
+ * We likely got here because report descriptor request timed
+ * out. Let's disconnect and destroy the hid_device structure.
+ */
+ hid = spi_hid_disconnect_hid(shid);
+ if (hid)
+ hid_destroy_device(hid);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void spi_hid_create_device_work(struct work_struct *work)
+{
+ struct spi_hid *shid =
+ container_of(work, struct spi_hid, create_device_work);
+ struct device *dev = &shid->spi->dev;
+ u8 prev_state = shid->power_state;
+ int ret;
+
+ trace_spi_hid_create_device_work(shid);
+
+ mutex_lock(&shid->power_lock);
+ if (prev_state == SPI_HID_POWER_MODE_OFF) {
+ dev_err(dev, "%s: Powered off, returning", __func__);
+ goto out;
+ }
+
+ ret = spi_hid_create_device(shid);
+ if (ret) {
+ dev_err(dev, "%s: Failed to create hid device\n", __func__);
+ goto out;
+ }
+
+ spi_hid_suspend(shid);
+
+out:
+ mutex_unlock(&shid->power_lock);
+
+ dev_dbg(dev, "%s: %s -> %s\n", __func__,
+ spi_hid_power_mode_string(prev_state),
+ spi_hid_power_mode_string(shid->power_state));
+}
+
+static void spi_hid_refresh_device_work(struct work_struct *work)
+{
+ struct spi_hid *shid =
+ container_of(work, struct spi_hid, refresh_device_work);
+ struct device *dev = &shid->spi->dev;
+ struct hid_device *hid;
+ int ret;
+ u32 new_crc32;
+
+ trace_spi_hid_refresh_device_work(shid);
+
+ mutex_lock(&shid->power_lock);
+ if (shid->power_state == SPI_HID_POWER_MODE_OFF) {
+ dev_err(dev, "%s: Powered off, returning", __func__);
+ goto out2;
+ }
+
+ mutex_lock(&shid->output_lock);
+ ret = spi_hid_report_descriptor_request(shid);
+ mutex_unlock(&shid->output_lock);
+ if (ret < 0) {
+ dev_err(dev,
+ "Refresh: failed report descriptor request, error %d",
+ ret);
+ goto out2;
+ }
+
+ new_crc32 = crc32_le(0, (unsigned char const *)shid->response.content,
+ (size_t)ret);
+ if (new_crc32 == shid->report_descriptor_crc32)
+ goto out1;
+
+ shid->report_descriptor_crc32 = new_crc32;
+ shid->refresh_in_progress = true;
+
+ hid = spi_hid_disconnect_hid(shid);
+ if (hid)
+ hid_destroy_device(hid);
+
+ ret = spi_hid_create_device(shid);
+ if (ret) {
+ dev_err(dev, "%s: Failed to create hid device\n", __func__);
+ goto out2;
+ }
+
+ shid->refresh_in_progress = false;
+
+out1:
+ if (completion_done(&shid->ready_done))
+ dev_err(&shid->spi->dev, "Nobody waiting for ready_done\n");
+ else
+ complete(&shid->ready_done);
+out2:
+ mutex_unlock(&shid->power_lock);
+}
+
+static int spi_hid_get_request(struct spi_hid *shid, u8 content_id)
+{
+ int ret;
+ struct device *dev = &shid->spi->dev;
+ struct spi_hid_output_report report = {
+ .report_type = SPI_HID_OUTPUT_REPORT_TYPE_HID_GET_FEATURE,
+ .content_length = 0,
+ .content_id = content_id,
+ .content = NULL,
+ };
+
+ ret = spi_hid_sync_request(shid, &report);
+ if (ret) {
+ dev_err(dev,
+ "Expected get request response not received! Error %d\n",
+ ret);
+ schedule_work(&shid->error_work);
+ }
+
+ return ret;
+}
+
+static int spi_hid_set_request(struct spi_hid *shid,
+ u8 *arg_buf, u16 arg_len, u8 content_id)
+{
+ struct spi_hid_output_report report = {
+ .report_type = SPI_HID_OUTPUT_REPORT_TYPE_HID_SET_FEATURE,
+ .content_length = arg_len,
+ .content_id = content_id,
+ .content = arg_buf,
+ };
+
+ return spi_hid_sync_request(shid, &report);
+}
+
+static irqreturn_t spi_hid_dev_irq(int irq, void *_shid)
+{
+ struct spi_hid *shid = _shid;
+ struct device *dev = &shid->spi->dev;
+ struct spi_hid_input_header header;
+ int ret = 0;
+
+ trace_spi_hid_dev_irq(shid, irq);
+ trace_spi_hid_header_transfer(shid);
+
+ ret = spi_hid_input_sync(shid, shid->input.header,
+ sizeof(shid->input.header), true);
+ if (ret) {
+ dev_err(dev, "Failed to transfer header: %d\n", ret);
+ goto out;
+ }
+
+ if (shid->power_state == SPI_HID_POWER_MODE_OFF) {
+ dev_warn(dev, "Device is off after header was received\n");
+ goto out;
+ }
+
+ trace_spi_hid_input_header_complete(shid,
+ shid->input_transfer[0].tx_buf,
+ shid->input_transfer[0].len,
+ shid->input_transfer[1].rx_buf,
+ shid->input_transfer[1].len,
+ shid->input_message.status);
+
+ if (shid->input_message.status < 0) {
+ dev_warn(dev, "Error reading header: %d\n",
+ shid->input_message.status);
+ shid->bus_error_count++;
+ shid->bus_last_error = shid->input_message.status;
+ schedule_work(&shid->error_work);
+ goto out;
+ }
+
+ spi_hid_populate_input_header(shid->input.header, &header);
+
+ ret = spi_hid_bus_validate_header(shid, &header);
+ if (ret) {
+ dev_err(dev, "Failed to validate header: %d\n", ret);
+ print_hex_dump(KERN_ERR, "spi_hid: header buffer: ",
+ DUMP_PREFIX_NONE, 16, 1,
+ shid->input.header,
+ sizeof(shid->input.header),
+ false);
+ shid->bus_error_count++;
+ shid->bus_last_error = ret;
+ goto out;
+ }
+
+ ret = spi_hid_input_sync(shid, shid->input.body, header.report_length,
+ false);
+ if (ret)
+ dev_err(dev, "Failed to transfer body: %d\n", ret);
+
+ if (shid->power_state == SPI_HID_POWER_MODE_OFF) {
+ dev_warn(dev, "Device is off after body was received\n");
+ goto out;
+ }
+
+ trace_spi_hid_input_body_complete(shid, shid->input_transfer[0].tx_buf,
+ shid->input_transfer[0].len,
+ shid->input_transfer[1].rx_buf,
+ shid->input_transfer[1].len,
+ shid->input_message.status);
+
+ if (shid->input_message.status < 0) {
+ dev_warn(dev, "Error reading body: %d\n",
+ shid->input_message.status);
+ shid->bus_error_count++;
+ shid->bus_last_error = shid->input_message.status;
+ schedule_work(&shid->error_work);
+ goto out;
+ }
+
+ spi_hid_process_input_report(shid, &shid->input);
+
+out:
+ return IRQ_HANDLED;
+}
+
+/* hid_ll_driver interface functions */
+
+static int spi_hid_ll_start(struct hid_device *hid)
+{
+ struct spi_device *spi = hid->driver_data;
+ struct spi_hid *shid = spi_get_drvdata(spi);
+
+ if (shid->desc.max_input_length < HID_MIN_BUFFER_SIZE) {
+ dev_err(&shid->spi->dev,
+ "HID_MIN_BUFFER_SIZE > max_input_length (%d)\n",
+ shid->desc.max_input_length);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void spi_hid_ll_stop(struct hid_device *hid)
+{
+ hid->claimed = 0;
+}
+
+static int spi_hid_ll_open(struct hid_device *hid)
+{
+ struct spi_device *spi = hid->driver_data;
+ struct spi_hid *shid = spi_get_drvdata(spi);
+ struct device *dev = &spi->dev;
+ u8 prev_state = shid->power_state;
+ int ret;
+
+ if (shid->refresh_in_progress)
+ return 0;
+
+ spi_hid_resume(shid);
+
+ dev_dbg(dev, "%s: %s -> %s\n", __func__,
+ spi_hid_power_mode_string(prev_state),
+ spi_hid_power_mode_string(shid->power_state));
+
+ reinit_completion(&shid->ready_done);
+ ret = wait_for_completion_interruptible_timeout(&shid->ready_done,
+ msecs_to_jiffies(1000));
+ if (ret == 0) {
+ dev_err(dev, "Handshake timed out\n");
+ return -EAGAIN;
+ } else {
+ dev_dbg(dev, "%s: ready\n", __func__);
+ shid->ready = true;
+ return 0;
+ }
+}
+
+static void spi_hid_ll_close(struct hid_device *hid)
+{
+ struct spi_device *spi = hid->driver_data;
+ struct spi_hid *shid = spi_get_drvdata(spi);
+ struct device *dev = &spi->dev;
+ u8 prev_state = shid->power_state;
+
+ if (shid->refresh_in_progress)
+ return;
+
+ mutex_lock(&shid->power_lock);
+ spi_hid_suspend(shid);
+ mutex_unlock(&shid->power_lock);
+
+ shid->attempts = 0;
+
+ dev_dbg(dev, "%s: %s -> %s\n", __func__,
+ spi_hid_power_mode_string(prev_state),
+ spi_hid_power_mode_string(shid->power_state));
+}
+
+static int spi_hid_ll_power(struct hid_device *hid, int level)
+{
+ struct spi_device *spi = hid->driver_data;
+ struct spi_hid *shid = spi_get_drvdata(spi);
+ int ret = 0;
+
+ mutex_lock(&shid->output_lock);
+ if (!shid->hid)
+ ret = -ENODEV;
+ mutex_unlock(&shid->output_lock);
+
+ return ret;
+}
+
+static int spi_hid_ll_parse(struct hid_device *hid)
+{
+ struct spi_device *spi = hid->driver_data;
+ struct spi_hid *shid = spi_get_drvdata(spi);
+ struct device *dev = &spi->dev;
+ int ret, len;
+
+ mutex_lock(&shid->output_lock);
+
+ len = spi_hid_report_descriptor_request(shid);
+ if (len < 0) {
+ dev_err(dev, "Report descriptor request failed, %d\n", len);
+ ret = len;
+ goto out;
+ }
+
+ /*
+ * FIXME: below call returning 0 doesn't mean that the report descriptor
+ * is good. We might be caching a crc32 of a corrupted r. d. or who
+ * knows what the FW sent. Need to have a feedback loop about r. d.
+ * being ok and only then cache it.
+ */
+ ret = hid_parse_report(hid, (u8 *)shid->response.content, len);
+ if (ret)
+ dev_err(dev, "failed parsing report: %d\n", ret);
+ else
+ shid->report_descriptor_crc32 = crc32_le(0,
+ (unsigned char const *)shid->response.content,
+ len);
+
+out:
+ mutex_unlock(&shid->output_lock);
+
+ return ret;
+}
+
+static int spi_hid_ll_raw_request(struct hid_device *hid,
+ unsigned char reportnum, u8 *buf, size_t len,
+ unsigned char rtype, int reqtype)
+{
+ struct spi_device *spi = hid->driver_data;
+ struct spi_hid *shid = spi_get_drvdata(spi);
+ struct device *dev = &spi->dev;
+ int ret;
+
+ if (!shid->ready) {
+ dev_err(&shid->spi->dev, "%s called in unready state\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ mutex_lock(&shid->output_lock);
+
+ switch (reqtype) {
+ case HID_REQ_SET_REPORT:
+ if (buf[0] != reportnum) {
+ dev_err(dev, "report id mismatch\n");
+ ret = -EINVAL;
+ break;
+ }
+
+ ret = spi_hid_set_request(shid, &buf[1], len - 1,
+ reportnum);
+ if (ret) {
+ dev_err(dev, "failed to set report\n");
+ break;
+ }
+
+ ret = len;
+ break;
+ case HID_REQ_GET_REPORT:
+ ret = spi_hid_get_request(shid, reportnum);
+ if (ret) {
+ dev_err(dev, "failed to get report\n");
+ break;
+ }
+
+ ret = min_t(size_t, len,
+ shid->response.body[1] | (shid->response.body[2] << 8));
+ memcpy(buf, &shid->response.content, ret);
+ break;
+ default:
+ dev_err(dev, "invalid request type\n");
+ ret = -EIO;
+ }
+
+ mutex_unlock(&shid->output_lock);
+
+ return ret;
+}
+
+static int spi_hid_ll_output_report(struct hid_device *hid,
+ u8 *buf, size_t len)
+{
+ int ret;
+ struct spi_device *spi = hid->driver_data;
+ struct spi_hid *shid = spi_get_drvdata(spi);
+ struct device *dev = &spi->dev;
+ struct spi_hid_output_report report = {
+ .report_type = SPI_HID_OUTPUT_REPORT_TYPE_HID_OUTPUT_REPORT,
+ .content_length = len - 1,
+ .content_id = buf[0],
+ .content = &buf[1],
+ };
+
+ mutex_lock(&shid->output_lock);
+ if (!shid->ready) {
+ dev_err(dev, "%s called in unready state\n", __func__);
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (shid->desc.no_output_report_ack)
+ ret = spi_hid_send_output_report(shid, &report);
+ else
+ ret = spi_hid_sync_request(shid, &report);
+
+ if (ret)
+ dev_err(dev, "failed to send output report\n");
+
+out:
+ mutex_unlock(&shid->output_lock);
+
+ if (ret > 0)
+ return -ret;
+
+ if (ret < 0)
+ return ret;
+
+ return len;
+}
+
+static struct hid_ll_driver spi_hid_ll_driver = {
+ .start = spi_hid_ll_start,
+ .stop = spi_hid_ll_stop,
+ .open = spi_hid_ll_open,
+ .close = spi_hid_ll_close,
+ .power = spi_hid_ll_power,
+ .parse = spi_hid_ll_parse,
+ .output_report = spi_hid_ll_output_report,
+ .raw_request = spi_hid_ll_raw_request,
+};
+
+const struct of_device_id spi_hid_of_match[] = {
+ { .compatible = "hid-over-spi" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, spi_hid_of_match);
+
+static ssize_t bus_error_count_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct spi_hid *shid = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d (%d)\n",
+ shid->bus_error_count, shid->bus_last_error);
+}
+static DEVICE_ATTR_RO(bus_error_count);
+
+static ssize_t regulator_error_count_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct spi_hid *shid = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d (%d)\n",
+ shid->regulator_error_count,
+ shid->regulator_last_error);
+}
+static DEVICE_ATTR_RO(regulator_error_count);
+
+static ssize_t device_initiated_reset_count_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct spi_hid *shid = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", shid->dir_count);
+}
+static DEVICE_ATTR_RO(device_initiated_reset_count);
+
+static const struct attribute *const spi_hid_attributes[] = {
+ &dev_attr_bus_error_count.attr,
+ &dev_attr_regulator_error_count.attr,
+ &dev_attr_device_initiated_reset_count.attr,
+ NULL /* Terminator */
+};
+
+static int spi_hid_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct spi_hid *shid;
+ unsigned long irqflags;
+ int ret;
+
+ if (spi->irq <= 0) {
+ dev_err(dev, "Missing IRQ\n");
+ ret = spi->irq ?: -EINVAL;
+ goto err0;
+ }
+
+ shid = devm_kzalloc(dev, sizeof(struct spi_hid), GFP_KERNEL);
+ if (!shid) {
+ ret = -ENOMEM;
+ goto err0;
+ }
+
+ shid->spi = spi;
+ shid->power_state = SPI_HID_POWER_MODE_ON;
+ spi_set_drvdata(spi, shid);
+
+ ret = sysfs_create_files(&dev->kobj, spi_hid_attributes);
+ if (ret) {
+ dev_err(dev, "Unable to create sysfs attributes\n");
+ goto err0;
+ }
+
+ ret = spi_hid_of_populate_config(&shid->conf, dev);
+ if (ret) {
+ dev_err(dev, "%s: unable to populate config data\n", __func__);
+ goto err1;
+ }
+
+ /* Using now populated conf let's pre-calculate the read approvals */
+ spi_hid_populate_read_approvals(&shid->conf, shid->read_approval_header,
+ shid->read_approval_body);
+
+ mutex_init(&shid->output_lock);
+ mutex_init(&shid->power_lock);
+ init_completion(&shid->output_done);
+ init_completion(&shid->ready_done);
+
+ INIT_WORK(&shid->reset_response_work, spi_hid_reset_response_work);
+ INIT_WORK(&shid->create_device_work, spi_hid_create_device_work);
+ INIT_WORK(&shid->refresh_device_work, spi_hid_refresh_device_work);
+ INIT_WORK(&shid->error_work, spi_hid_error_work);
+
+ /*
+ * At the end of probe we initialize the device:
+ * 0) Default pinctrl in DT: assert reset, bias the interrupt line
+ * 1) sleep minimal reset delay
+ * 2) request IRQ
+ * 3) power up the device
+ * 4) deassert reset (high)
+ * After this we expect an IRQ with a reset response.
+ */
+
+ spi_hid_of_sleep_minimal_reset_delay(&shid->conf);
+
+ irqflags = irq_get_trigger_type(spi->irq) | IRQF_ONESHOT;
+ ret = devm_request_threaded_irq(dev, spi->irq, NULL, spi_hid_dev_irq,
+ irqflags, dev_name(&spi->dev), shid);
+ if (ret) {
+ dev_err(dev, "%s: unable to request threaded IRQ\n", __func__);
+ goto err1;
+ }
+ shid->irq_enabled = true;
+
+ ret = spi_hid_of_power_up(&shid->conf);
+ if (ret) {
+ dev_err(dev, "%s: could not power up\n", __func__);
+ shid->regulator_error_count++;
+ shid->regulator_last_error = ret;
+ goto err1;
+ }
+
+ spi_hid_of_deassert_reset(&shid->conf);
+
+ dev_dbg(dev, "%s: d3 -> %s\n", __func__,
+ spi_hid_power_mode_string(shid->power_state));
+
+ return 0;
+
+err1:
+ sysfs_remove_files(&dev->kobj, spi_hid_attributes);
+
+err0:
+ return ret;
+}
+
+static int spi_hid_remove(struct spi_device *spi)
+{
+ struct spi_hid *shid = spi_get_drvdata(spi);
+ struct device *dev = &spi->dev;
+ int ret;
+
+ spi_hid_of_assert_reset(&shid->conf);
+ ret = spi_hid_of_power_down(&shid->conf);
+ if (ret) {
+ dev_err(dev, "failed to disable regulator\n");
+ shid->regulator_error_count++;
+ shid->regulator_last_error = ret;
+ }
+ sysfs_remove_files(&dev->kobj, spi_hid_attributes);
+ spi_hid_stop_hid(shid);
+
+ return 0;
+}
+
+static const struct spi_device_id spi_hid_id_table[] = {
+ { "hid", 0 },
+ { "hid-over-spi", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(spi, spi_hid_id_table);
+
+static struct spi_driver spi_hid_driver = {
+ .driver = {
+ .name = "spi_hid",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(spi_hid_of_match),
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = spi_hid_probe,
+ .remove = spi_hid_remove,
+ .id_table = spi_hid_id_table,
+};
+
+module_spi_driver(spi_hid_driver);
+
+MODULE_DESCRIPTION("HID over SPI transport driver");
+MODULE_AUTHOR("Dmitry Antipov <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/spi-hid/spi-hid-core.h b/drivers/hid/spi-hid/spi-hid-core.h
new file mode 100644
index 000000000000..45cf3fd2b369
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-core.h
@@ -0,0 +1,188 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Microsoft Corporation
+ */
+
+#ifndef SPI_HID_CORE_H
+#define SPI_HID_CORE_H
+
+#include <linux/completion.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include "spi-hid-of.h"
+
+/* Protocol constants */
+#define SPI_HID_READ_APPROVAL_CONSTANT 0xff
+#define SPI_HID_INPUT_HEADER_SYNC_BYTE 0x5a
+#define SPI_HID_INPUT_HEADER_VERSION 0x03
+#define SPI_HID_SUPPORTED_VERSION 0x0300
+
+/* Protocol message size constants */
+#define SPI_HID_READ_APPROVAL_LEN 5
+#define SPI_HID_INPUT_HEADER_LEN 4
+#define SPI_HID_INPUT_BODY_LEN 4
+#define SPI_HID_OUTPUT_HEADER_LEN 8
+#define SPI_HID_DEVICE_DESCRIPTOR_LENGTH 24
+
+/* Protocol message type constants */
+#define SPI_HID_INPUT_REPORT_TYPE_DATA 0x01
+#define SPI_HID_INPUT_REPORT_TYPE_RESET_RESP 0x03
+#define SPI_HID_INPUT_REPORT_TYPE_COMMAND_RESP 0x04
+#define SPI_HID_INPUT_REPORT_TYPE_GET_FEATURE_RESP 0x05
+#define SPI_HID_INPUT_REPORT_TYPE_DEVICE_DESC 0x07
+#define SPI_HID_INPUT_REPORT_TYPE_REPORT_DESC 0x08
+#define SPI_HID_INPUT_REPORT_TYPE_SET_FEATURE_RESP 0x09
+#define SPI_HID_INPUT_REPORT_TYPE_SET_OUTPUT_REPORT_RESP 0x0a
+#define SPI_HID_INPUT_REPORT_TYPE_GET_INPUT_REPORT_RESP 0x0b
+
+#define SPI_HID_OUTPUT_REPORT_TYPE_DEVICE_DESC_REQUEST 0x01
+#define SPI_HID_OUTPUT_REPORT_TYPE_REPORT_DESC_REQUEST 0x02
+#define SPI_HID_OUTPUT_REPORT_TYPE_HID_SET_FEATURE 0x03
+#define SPI_HID_OUTPUT_REPORT_TYPE_HID_GET_FEATURE 0x04
+#define SPI_HID_OUTPUT_REPORT_TYPE_HID_OUTPUT_REPORT 0x05
+#define SPI_HID_OUTPUT_REPORT_TYPE_INPUT_REPORT_REQUEST 0x06
+#define SPI_HID_OUTPUT_REPORT_TYPE_COMMAND 0x07
+
+#define SPI_HID_OUTPUT_REPORT_CONTENT_ID_DESC_REQUEST 0x00
+
+/* Power mode constants */
+#define SPI_HID_POWER_MODE_ON 0x01
+#define SPI_HID_POWER_MODE_SLEEP 0x02
+#define SPI_HID_POWER_MODE_OFF 0x03
+#define SPI_HID_POWER_MODE_WAKING_SLEEP 0x04
+
+/* Raw input buffer with data from the bus */
+struct spi_hid_input_buf {
+ u8 header[SPI_HID_INPUT_HEADER_LEN];
+ u8 body[SPI_HID_INPUT_BODY_LEN];
+ u8 content[SZ_8K];
+};
+
+/* Processed data from input report header */
+struct spi_hid_input_header {
+ u8 version;
+ u16 report_length;
+ u8 last_fragment_flag;
+ u8 sync_const;
+};
+
+/* Processed data from input report body, excluding the content */
+struct spi_hid_input_body {
+ u8 report_type;
+ u16 content_length;
+ u8 content_id;
+};
+
+/* Processed data from an input report */
+struct spi_hid_input_report {
+ u8 report_type;
+ u16 content_length;
+ u8 content_id;
+ u8 *content;
+};
+
+/* Raw output report buffer to be put on the bus */
+struct spi_hid_output_buf {
+ u8 header[SPI_HID_OUTPUT_HEADER_LEN];
+ u8 content[SZ_8K];
+};
+
+/* Data necessary to send an output report */
+struct spi_hid_output_report {
+ u8 report_type;
+ u16 content_length;
+ u8 content_id;
+ u8 *content;
+};
+
+/* Raw content in device descriptor */
+struct spi_hid_device_desc_raw {
+ __le16 wDeviceDescLength;
+ __le16 bcdVersion;
+ __le16 wReportDescLength;
+ __le16 wMaxInputLength;
+ __le16 wMaxOutputLength;
+ __le16 wMaxFragmentLength;
+ __le16 wVendorID;
+ __le16 wProductID;
+ __le16 wVersionID;
+ __le16 wFlags;
+ u8 reserved[4];
+} __packed;
+
+/* Processed data from a device descriptor */
+struct spi_hid_device_descriptor {
+ u16 hid_version;
+ u16 report_descriptor_length;
+ u16 max_input_length;
+ u16 max_output_length;
+ u16 max_fragment_length;
+ u16 vendor_id;
+ u16 product_id;
+ u16 version_id;
+ u8 no_output_report_ack;
+};
+
+/* Driver context */
+struct spi_hid {
+ struct spi_device *spi;
+ struct hid_device *hid;
+
+ struct spi_transfer input_transfer[2];
+ struct spi_transfer output_transfer;
+ struct spi_message input_message;
+ struct spi_message output_message;
+
+ struct spi_hid_of_config conf;
+ struct spi_hid_device_descriptor desc;
+ struct spi_hid_output_buf output;
+ struct spi_hid_input_buf input;
+ struct spi_hid_input_buf response;
+
+ u32 input_transfer_pending;
+
+ u8 power_state;
+
+ u8 attempts;
+
+ /*
+ * ready flag indicates that the FW is ready to accept commands and
+ * requests. The FW becomes ready after sending the report descriptor.
+ */
+ bool ready;
+ /*
+ * refresh_in_progress is set to true while the refresh_device worker
+ * thread is destroying and recreating the hidraw device. When this flag
+ * is set to true, the ll_close and ll_open functions will not cause
+ * power state changes.
+ */
+ bool refresh_in_progress;
+
+ bool irq_enabled;
+
+ struct work_struct reset_response_work;
+ struct work_struct create_device_work;
+ struct work_struct refresh_device_work;
+ struct work_struct error_work;
+
+ struct mutex output_lock;
+ struct mutex power_lock;
+ struct completion output_done;
+ struct completion ready_done;
+
+ u8 read_approval_header[SPI_HID_READ_APPROVAL_LEN];
+ u8 read_approval_body[SPI_HID_READ_APPROVAL_LEN];
+
+ u32 report_descriptor_crc32;
+
+ u32 regulator_error_count;
+ int regulator_last_error;
+ u32 bus_error_count;
+ int bus_last_error;
+ u32 dir_count;
+};
+
+#endif
diff --git a/drivers/hid/spi-hid/spi-hid-of.c b/drivers/hid/spi-hid/spi-hid-of.c
new file mode 100644
index 000000000000..d8d1d134cf29
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-of.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HID over SPI protocol, Open Firmware related code
+ *
+ * Copyright (c) 2021 Microsoft Corporation
+ *
+ * This code was forked out of the HID over SPI core code, which is partially
+ * based on "HID over I2C protocol implementation:
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <[email protected]>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ *
+ * which in turn is partially based on "USB HID support for Linux":
+ *
+ * Copyright (c) 1999 Andreas Gal
+ * Copyright (c) 2000-2005 Vojtech Pavlik <[email protected]>
+ * Copyright (c) 2005 Michael Haboustak <[email protected]> for Concept2, Inc
+ * Copyright (c) 2007-2008 Oliver Neukum
+ * Copyright (c) 2006-2010 Jiri Kosina
+ */
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+
+#include "spi-hid-core.h"
+
+int spi_hid_of_populate_config(struct spi_hid_of_config *conf,
+ struct device *dev)
+{
+ int ret;
+ u32 val;
+
+ ret = device_property_read_u32(dev, "input-report-header-address",
+ &val);
+ if (ret) {
+ dev_err(dev, "Input report header address not provided\n");
+ return -ENODEV;
+ }
+ conf->input_report_header_address = val;
+
+ ret = device_property_read_u32(dev, "input-report-body-address", &val);
+ if (ret) {
+ dev_err(dev, "Input report body address not provided\n");
+ return -ENODEV;
+ }
+ conf->input_report_body_address = val;
+
+ ret = device_property_read_u32(dev, "output-report-address", &val);
+ if (ret) {
+ dev_err(dev, "Output report address not provided\n");
+ return -ENODEV;
+ }
+ conf->output_report_address = val;
+
+ ret = device_property_read_u32(dev, "read-opcode", &val);
+ if (ret) {
+ dev_err(dev, "Read opcode not provided\n");
+ return -ENODEV;
+ }
+ conf->read_opcode = val;
+
+ ret = device_property_read_u32(dev, "write-opcode", &val);
+ if (ret) {
+ dev_err(dev, "Write opcode not provided\n");
+ return -ENODEV;
+ }
+ conf->write_opcode = val;
+
+ ret = device_property_read_u32(dev, "post-power-on-delay-ms", &val);
+ if (ret) {
+ dev_err(dev, "post-power-on-delay-ms not provided, using 10\n");
+ val = 10;
+ }
+ conf->post_power_on_delay_ms = val;
+
+ ret = device_property_read_u32(dev, "minimal-reset-delay-ms", &val);
+ if (ret) {
+ dev_err(dev, "minimal-reset-delay-ms not provided, using 100\n");
+ val = 100;
+ }
+ conf->minimal_reset_delay_ms = val;
+
+ /* FIXME: not reading hid-over-spi-flags, multi-SPI not supported */
+
+ conf->supply = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(conf->supply)) {
+ if (PTR_ERR(conf->supply) != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get regulator: %ld\n",
+ PTR_ERR(conf->supply));
+ return PTR_ERR(conf->supply);
+ }
+
+ conf->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(conf->reset_gpio)) {
+ dev_err(dev, "%s: error getting reset GPIO\n", __func__);
+ return PTR_ERR(conf->reset_gpio);
+ }
+
+ return 0;
+}
+
+int spi_hid_of_power_down(struct spi_hid_of_config *conf)
+{
+ if (regulator_is_enabled(conf->supply) == 0)
+ return 0;
+
+ return regulator_disable(conf->supply);
+}
+
+int spi_hid_of_power_up(struct spi_hid_of_config *conf)
+{
+ int ret;
+
+ if (regulator_is_enabled(conf->supply) > 0)
+ return 0;
+
+ ret = regulator_enable(conf->supply);
+
+ usleep_range(1000 * conf->post_power_on_delay_ms,
+ 1000 * (conf->post_power_on_delay_ms + 1));
+
+ return ret;
+}
+
+void spi_hid_of_assert_reset(struct spi_hid_of_config *conf)
+{
+ gpiod_set_value(conf->reset_gpio, 1);
+}
+
+void spi_hid_of_deassert_reset(struct spi_hid_of_config *conf)
+{
+ gpiod_set_value(conf->reset_gpio, 0);
+}
+
+void spi_hid_of_sleep_minimal_reset_delay(struct spi_hid_of_config *conf)
+{
+ usleep_range(1000 * conf->minimal_reset_delay_ms,
+ 1000 * (conf->minimal_reset_delay_ms + 1));
+}
diff --git a/drivers/hid/spi-hid/spi-hid-of.h b/drivers/hid/spi-hid/spi-hid-of.h
new file mode 100644
index 000000000000..48d9dfe44e95
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-of.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Microsoft Corporation
+ */
+
+#ifndef SPI_HID_OF_H
+#define SPI_HID_OF_H
+
+/* Config structure is filled with data from Device Tree */
+struct spi_hid_of_config {
+ u32 input_report_header_address;
+ u32 input_report_body_address;
+ u32 output_report_address;
+ u8 read_opcode;
+ u8 write_opcode;
+ u32 post_power_on_delay_ms;
+ u32 minimal_reset_delay_ms;
+ struct gpio_desc *reset_gpio;
+ struct regulator *supply;
+};
+
+int spi_hid_of_populate_config(struct spi_hid_of_config *conf,
+ struct device *dev);
+int spi_hid_of_power_down(struct spi_hid_of_config *conf);
+int spi_hid_of_power_up(struct spi_hid_of_config *conf);
+void spi_hid_of_assert_reset(struct spi_hid_of_config *conf);
+void spi_hid_of_deassert_reset(struct spi_hid_of_config *conf);
+void spi_hid_of_sleep_minimal_reset_delay(struct spi_hid_of_config *conf);
+
+#endif
diff --git a/drivers/hid/spi-hid/spi-hid_trace.h b/drivers/hid/spi-hid/spi-hid_trace.h
new file mode 100644
index 000000000000..23732ba493a0
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid_trace.h
@@ -0,0 +1,194 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Microsoft Corporation
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM spi_hid
+
+#if !defined(_SPI_HID_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _SPI_HID_TRACE_H
+
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+#include "spi-hid-core.h"
+
+DECLARE_EVENT_CLASS(spi_hid_transfer,
+ TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len,
+ const void *rx_buf, u16 rx_len, int ret),
+
+ TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret),
+
+ TP_STRUCT__entry(
+ __field(int, bus_num)
+ __field(int, chip_select)
+ __field(int, len)
+ __field(int, ret)
+ __dynamic_array(u8, rx_buf, rx_len)
+ __dynamic_array(u8, tx_buf, tx_len)
+ ),
+
+ TP_fast_assign(
+ __entry->bus_num = shid->spi->controller->bus_num;
+ __entry->chip_select = shid->spi->chip_select;
+ __entry->len = rx_len + tx_len;
+ __entry->ret = ret;
+
+ memcpy(__get_dynamic_array(tx_buf), tx_buf, tx_len);
+ memcpy(__get_dynamic_array(rx_buf), rx_buf, rx_len);
+ ),
+
+ TP_printk("spi%d.%d: len=%d tx=[%*phD] rx=[%*phD] --> %d",
+ __entry->bus_num, __entry->chip_select, __entry->len,
+ __get_dynamic_array_len(tx_buf), __get_dynamic_array(tx_buf),
+ __get_dynamic_array_len(rx_buf), __get_dynamic_array(rx_buf),
+ __entry->ret)
+);
+
+DEFINE_EVENT(spi_hid_transfer, spi_hid_input_sync,
+ TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len,
+ const void *rx_buf, u16 rx_len, int ret),
+ TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret)
+);
+
+DEFINE_EVENT(spi_hid_transfer, spi_hid_input_header_complete,
+ TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len,
+ const void *rx_buf, u16 rx_len, int ret),
+ TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret)
+);
+
+DEFINE_EVENT(spi_hid_transfer, spi_hid_input_body_complete,
+ TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len,
+ const void *rx_buf, u16 rx_len, int ret),
+ TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret)
+);
+
+DEFINE_EVENT(spi_hid_transfer, spi_hid_output_begin,
+ TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len,
+ const void *rx_buf, u16 rx_len, int ret),
+ TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret)
+);
+
+DEFINE_EVENT(spi_hid_transfer, spi_hid_output_end,
+ TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len,
+ const void *rx_buf, u16 rx_len, int ret),
+ TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret)
+);
+
+DECLARE_EVENT_CLASS(spi_hid_irq,
+ TP_PROTO(struct spi_hid *shid, int irq),
+
+ TP_ARGS(shid, irq),
+
+ TP_STRUCT__entry(
+ __field(int, bus_num)
+ __field(int, chip_select)
+ __field(int, irq)
+ ),
+
+ TP_fast_assign(
+ __entry->bus_num = shid->spi->controller->bus_num;
+ __entry->chip_select = shid->spi->chip_select;
+ __entry->irq = irq;
+ ),
+
+ TP_printk("spi%d.%d: IRQ %d",
+ __entry->bus_num, __entry->chip_select, __entry->irq)
+);
+
+DEFINE_EVENT(spi_hid_irq, spi_hid_dev_irq,
+ TP_PROTO(struct spi_hid *shid, int irq),
+ TP_ARGS(shid, irq)
+);
+
+DECLARE_EVENT_CLASS(spi_hid,
+ TP_PROTO(struct spi_hid *shid),
+
+ TP_ARGS(shid),
+
+ TP_STRUCT__entry(
+ __field(int, bus_num)
+ __field(int, chip_select)
+ __field(int, power_state)
+ __field(bool, ready)
+
+ __field(int, vendor_id)
+ __field(int, product_id)
+ __field(int, max_input_length)
+ __field(int, max_output_length)
+ __field(u16, hid_version)
+ __field(u16, report_descriptor_length)
+ __field(u16, version_id)
+ ),
+
+ TP_fast_assign(
+ __entry->bus_num = shid->spi->controller->bus_num;
+ __entry->chip_select = shid->spi->chip_select;
+ __entry->power_state = shid->power_state;
+ __entry->ready = shid->ready;
+
+ __entry->vendor_id = shid->desc.vendor_id;
+ __entry->product_id = shid->desc.product_id;
+ __entry->max_input_length = shid->desc.max_input_length;
+ __entry->max_output_length = shid->desc.max_output_length;
+ __entry->hid_version = shid->desc.hid_version;
+ __entry->report_descriptor_length =
+ shid->desc.report_descriptor_length;
+ __entry->version_id = shid->desc.version_id;
+ ),
+
+ TP_printk("spi%d.%d: (%04x:%04x v%d) HID v%d.%d state i:%d p:%d len i:%d o:%d r:%d flags %c",
+ __entry->bus_num, __entry->chip_select, __entry->vendor_id,
+ __entry->product_id, __entry->version_id,
+ __entry->hid_version >> 8, __entry->hid_version & 0xff,
+ __entry->power_state, __entry->max_input_length,
+ __entry->max_output_length, __entry->report_descriptor_length,
+ __entry->ready ? 'R' : 'r')
+);
+
+DEFINE_EVENT(spi_hid, spi_hid_header_transfer,
+ TP_PROTO(struct spi_hid *shid),
+ TP_ARGS(shid)
+);
+
+DEFINE_EVENT(spi_hid, spi_hid_process_input_report,
+ TP_PROTO(struct spi_hid *shid),
+ TP_ARGS(shid)
+);
+
+DEFINE_EVENT(spi_hid, spi_hid_input_report_handler,
+ TP_PROTO(struct spi_hid *shid),
+ TP_ARGS(shid)
+);
+
+DEFINE_EVENT(spi_hid, spi_hid_reset_response_work,
+ TP_PROTO(struct spi_hid *shid),
+ TP_ARGS(shid)
+);
+
+DEFINE_EVENT(spi_hid, spi_hid_create_device_work,
+ TP_PROTO(struct spi_hid *shid),
+ TP_ARGS(shid)
+);
+
+DEFINE_EVENT(spi_hid, spi_hid_refresh_device_work,
+ TP_PROTO(struct spi_hid *shid),
+ TP_ARGS(shid)
+);
+
+DEFINE_EVENT(spi_hid, spi_hid_response_handler,
+ TP_PROTO(struct spi_hid *shid),
+ TP_ARGS(shid)
+);
+
+DEFINE_EVENT(spi_hid, spi_hid_error_work,
+ TP_PROTO(struct spi_hid *shid),
+ TP_ARGS(shid)
+);
+
+#endif /* _SPI_HID_TRACE_H */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#define TRACE_INCLUDE_FILE spi-hid_trace
+#include <trace/define_trace.h>
diff --git a/drivers/hid/spi-hid/trace.c b/drivers/hid/spi-hid/trace.c
new file mode 100644
index 000000000000..6cd686c9493e
--- /dev/null
+++ b/drivers/hid/spi-hid/trace.c
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 Microsoft Corporation
+ *
+ * Author: Felipe Balbi <[email protected]>
+ */
+
+#define CREATE_TRACE_POINTS
+#include "spi-hid_trace.h"
--
2.25.1

2022-07-07 17:23:49

by Jarrett Schultz

[permalink] [raw]
Subject: [PATCH v5 4/6] Documentation: Correction in HID output_report callback description.

From: Jarrett Schultz <[email protected]>

Originally output_report callback was described as must-be asynchronous,
but that is not the case in some implementations, namely i2c-hid.
Correct the documentation to say that it may be asynchronous.

Signed-off-by: Dmitry Antipov <[email protected]>
---
Documentation/hid/hid-transport.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Documentation/hid/hid-transport.rst b/Documentation/hid/hid-transport.rst
index 6f1692da296c..2008cf432af1 100644
--- a/Documentation/hid/hid-transport.rst
+++ b/Documentation/hid/hid-transport.rst
@@ -327,8 +327,8 @@ The available HID callbacks are:

Send raw output report via intr channel. Used by some HID device drivers
which require high throughput for outgoing requests on the intr channel. This
- must not cause SET_REPORT calls! This must be implemented as asynchronous
- output report on the intr channel!
+ must not cause SET_REPORT calls! This call might be asynchronous, so the
+ caller should not expect an immediate response!

::

--
2.25.1

2022-07-07 20:58:55

by Randy Dunlap

[permalink] [raw]
Subject: Re: [PATCH v5 5/6] HID: add spi-hid, transport driver for HID over SPI bus

Hi--

On 7/7/22 09:59, Jarrett Schultz wrote:
> From: Jarrett Schultz <[email protected]>
>
> This driver follows HID Over SPI Protocol Specification 1.0 available at
> https://www.microsoft.com/en-us/download/details.aspx?id=103325. The
> initial version of the driver does not support: 1) multi-fragment input
> reports, 2) sending GET_INPUT and COMMAND output report types and
> processing their respective acknowledge input reports, and 3) device
> sleep power state.
>
> Signed-off-by: Dmitry Antipov <[email protected]>

Missing your (Jarrett's) Signed-off-by: here.

More below...

> ---
> drivers/hid/Kconfig | 2 +
> drivers/hid/Makefile | 1 +
> drivers/hid/spi-hid/Kconfig | 25 +
> drivers/hid/spi-hid/Makefile | 12 +
> drivers/hid/spi-hid/spi-hid-core.c | 1326 +++++++++++++++++++++++++++
> drivers/hid/spi-hid/spi-hid-core.h | 188 ++++
> drivers/hid/spi-hid/spi-hid-of.c | 141 +++
> drivers/hid/spi-hid/spi-hid-of.h | 30 +
> drivers/hid/spi-hid/spi-hid_trace.h | 194 ++++
> drivers/hid/spi-hid/trace.c | 9 +
> 10 files changed, 1928 insertions(+)
> create mode 100644 drivers/hid/spi-hid/Kconfig
> create mode 100644 drivers/hid/spi-hid/Makefile
> create mode 100644 drivers/hid/spi-hid/spi-hid-core.c
> create mode 100644 drivers/hid/spi-hid/spi-hid-core.h
> create mode 100644 drivers/hid/spi-hid/spi-hid-of.c
> create mode 100644 drivers/hid/spi-hid/spi-hid-of.h
> create mode 100644 drivers/hid/spi-hid/spi-hid_trace.h
> create mode 100644 drivers/hid/spi-hid/trace.c
>

> diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig
> new file mode 100644
> index 000000000000..37302d658162
> --- /dev/null
> +++ b/drivers/hid/spi-hid/Kconfig
> @@ -0,0 +1,25 @@
> +#
> +# Copyright (c) 2021 Microsoft Corporation
> +#
> +# This program is free software; you can redistribute it and/or modify it
> +# under the terms of the GNU General Public License version 2 as published by
> +# the Free Software Foundation.
> +#

Please just use SPDX tags instead of all of that.
See other hid/Kconfig files for examples.

> +menu "SPI HID support"
> + depends on SPI
> +
> +config SPI_HID
> + tristate "HID over SPI transport layer"
> + default n
> + depends on SPI && INPUT && OF
> + select HID
> + help
> + Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
> + other HID based devices which is connected to your computer via SPI.

HID-based devices which are connected
(or)
HID-based device which is connected

> +
> + If unsure, say N.
> +
> + This support is also available as a module. If so, the module
> + will be called spi-hid.
> +
> +endmenu

--
~Randy

2022-07-11 22:50:54

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH v5 3/6] dt-bindings: input: Document Microsoft G6 Touch Digitizer

On Thu, Jul 07, 2022 at 09:58:59AM -0700, Jarrett Schultz wrote:
> From: Jarrett Schultz <[email protected]>
>
> Documentation describes the required and optional properties for
> implementing Device Tree for a Microsoft G6 Touch Digitizer that
> supports HID over SPI Protocol 1.0 specification.
>
> Signed-off-by: Dmitry Antipov <[email protected]>
> Signed-off-by: Jarrett Schultz <[email protected]>
> ---
> .../input/microsoft,g6-touch-digitizer.yaml | 135 ++++++++++++++++++
> 1 file changed, 135 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/input/microsoft,g6-touch-digitizer.yaml
>
> diff --git a/Documentation/devicetree/bindings/input/microsoft,g6-touch-digitizer.yaml b/Documentation/devicetree/bindings/input/microsoft,g6-touch-digitizer.yaml
> new file mode 100644
> index 000000000000..b607bbb32a42
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/microsoft,g6-touch-digitizer.yaml
> @@ -0,0 +1,135 @@
> +# SPDX-License-Identifier: (GPL-2.0-only or BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/input/microsoft,g6-touch-digitizer.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Microsoft G6 Touch Digitizer
> +
> +maintainers:
> + - Dmitry Antipov <[email protected]>
> +
> +description: |
> + Microsoft G6 touch digitizer is a HID over SPI device supporting HID Over SPI
> + Protocol Specification 1.0, available at
> + https://www.microsoft.com/en-us/download/details.aspx?id=103325.
> +
> +properties:
> + compatible:
> + items:
> + - const: microsoft,g6-touch-digitizer
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + reset-gpios:
> + maxItems: 1
> + description:
> + GPIO specifier for the digitizer's reset pin (active low). The line must
> + be flagged with GPIO_ACTIVE_LOW.
> +
> + vdd-supply:
> + description:
> + Regulator for the VDD supply voltage.
> +
> + input-report-header-address:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + minimum: 0
> + maximum: 16777215

0xffffff would be more readable IMO.

> + description:
> + A value to be included in the Read Approval packet, listing an address of
> + the input report header to be put on the SPI bus. This address has 24
> + bits.
> +
> + input-report-body-address:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + minimum: 0
> + maximum: 16777215
> + description:
> + A value to be included in the Read Approval packet, listing an address of
> + the input report body to be put on the SPI bus. This address has 24 bits.
> +
> + output-report-address:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + minimum: 0
> + maximum: 16777215
> + description:
> + A value to be included in the Output Report sent by the host, listing an
> + address where the output report on the SPI bus is to be written to. This
> + address has 24 bits.

Are all these properties common to HID-over-SPI or all of HID? If so,
then they should be in a common, shared schema doc and referenced here.

> +
> + post-power-on-delay-ms:
> + description:
> + Optional time in ms required by the device after enabling its regulators
> + or powering it on, before it is ready for communication.
> +
> + minimal-reset-delay-ms:
> + description:
> + Optional minimum amount of time in ms that device needs to be in reset
> + state for the reset to take effect.

These delays should really be implied by the compatible string.

> +
> + read-opcode:
> + description:
> + Value to be used in Read Approval packets. 1 byte.

Needs a type reference.

> +
> + write-opcode:
> + description:
> + Value to be used in Write Approval packets. 1 byte.

ditto

> +
> + hid-over-spi-flags:
> + description:
> + 16 bits.
> + Bits 0-12 - Reserved (must be 0)
> + Bit 13 - SPI Write Mode. Possible values -
> + * 0b0- Writes are carried out in Single-SPI mode
> + * 0b1- Writes are carried out in the Multi-SPI mode specified by bits
> + 14-15
> + Bits 14-15 - Multi-SPI Mode. Possible values -
> + * 0b00- Single SPI
> + * 0b01- Dual SPI
> + * 0b10- Quad SPI

ditto.

The existing SPI bus width properties wouldn't work here?

> +
> +required:
> + - compatible
> + - interrupts
> + - reset-gpios
> + - vdd-supply
> + - input-report-header-address
> + - input-report-body-address
> + - output-report-address
> + - read-opcode
> + - write-opcode
> + - hid-over-spi-flags
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> + #include <dt-bindings/gpio/gpio.h>
> +
> + spi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + hid@0 {
> + compatible = "microsoft,g6-touch-digitizer";
> + reg = <0x0>;
> + interrupts-extended = <&gpio 42 IRQ_TYPE_EDGE_FALLING>;
> + reset-gpios = <&gpio 27 GPIO_ACTIVE_LOW>;
> + vdd-supply = <&pm8350c_l3>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&ts_d6_reset_assert &ts_d6_int_bias>;
> + input-report-header-address = <0x1000>;
> + input-report-body-address = <0x1004>;
> + output-report-address = <0x2000>;
> + read-opcode = <0x0b>;
> + write-opcode = <0x02>;

Description says 8-bit, but this is 32-bits.

> + hid-over-spi-flags = <0x00>;

Description says 16-bits, but this is 32-bits.

> + post-power-on-delay-ms = <5>;
> + minimal-reset-delay-ms = <5>;
> + };
> + };
> --
> 2.25.1
>
>

2022-07-12 01:02:28

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v5 5/6] HID: add spi-hid, transport driver for HID over SPI bus

Hi Jarrett,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on hid/for-next]
[also build test WARNING on dtor-input/next robh/for-next linus/master v5.19-rc6 next-20220711]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Jarrett-Schultz/Add-spi-hid-transport-for-HID-over-SPI-bus/20220708-010203
base: https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-next
config: i386-allyesconfig (https://download.01.org/0day-ci/archive/20220712/[email protected]/config)
compiler: clang version 15.0.0 (https://github.com/llvm/llvm-project 77a38f6839980bfac61babb40d83772c51427011)
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/d0121c2f2d1bb21824555c34c233dd3fbc6aee96
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Jarrett-Schultz/Add-spi-hid-transport-for-HID-over-SPI-bus/20220708-010203
git checkout d0121c2f2d1bb21824555c34c233dd3fbc6aee96
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=i386 SHELL=/bin/bash drivers/hid/spi-hid/ drivers/md/ drivers/net/ethernet/marvell/prestera/ drivers/vfio/pci/mlx5/

If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <[email protected]>

All warnings (new ones prefixed by >>):

>> drivers/hid/spi-hid/spi-hid-core.c:193:20: warning: 'const' type qualifier on return type has no effect [-Wignored-qualifiers]
static const char *const spi_hid_power_mode_string(u8 power_state)
^~~~~~
>> drivers/hid/spi-hid/spi-hid-core.c:691:4: warning: format specifies type 'unsigned short' but the argument has type '__u32' (aka 'unsigned int') [-Wformat]
hid->vendor, hid->product);
^~~~~~~~~~~
drivers/hid/spi-hid/spi-hid-core.c:691:17: warning: format specifies type 'unsigned short' but the argument has type '__u32' (aka 'unsigned int') [-Wformat]
hid->vendor, hid->product);
^~~~~~~~~~~~
drivers/hid/spi-hid/spi-hid-core.c:1318:13: error: incompatible function pointer types initializing 'void (*)(struct spi_device *)' with an expression of type 'int (struct spi_device *)' [-Werror,-Wincompatible-function-pointer-types]
.remove = spi_hid_remove,
^~~~~~~~~~~~~~
3 warnings and 1 error generated.
--
In file included from drivers/hid/spi-hid/trace.c:9:
In file included from drivers/hid/spi-hid/./spi-hid_trace.h:194:
In file included from include/trace/define_trace.h:102:
In file included from include/trace/trace_events.h:237:
>> drivers/hid/spi-hid/./spi-hid_trace.h:140:92: warning: more '%' conversions than data arguments [-Wformat-insufficient-args]
TP_printk("spi%d.%d: (%04x:%04x v%d) HID v%d.%d state i:%d p:%d len i:%d o:%d r:%d flags %c",
~^
include/trace/stages/stage3_trace_output.h:9:33: note: expanded from macro 'TP_printk'
#define TP_printk(fmt, args...) fmt "\n", args
^~~
include/trace/trace_events.h:203:27: note: expanded from macro 'DECLARE_EVENT_CLASS'
trace_event_printf(iter, print); \
^~~~~
1 warning generated.


vim +/const +193 drivers/hid/spi-hid/spi-hid-core.c

192
> 193 static const char *const spi_hid_power_mode_string(u8 power_state)
194 {
195 switch (power_state) {
196 case SPI_HID_POWER_MODE_ON:
197 return "d0";
198 case SPI_HID_POWER_MODE_SLEEP:
199 return "d2";
200 case SPI_HID_POWER_MODE_OFF:
201 return "d3";
202 case SPI_HID_POWER_MODE_WAKING_SLEEP:
203 return "d3*";
204 default:
205 return "unknown";
206 }
207 }
208

--
0-DAY CI Kernel Test Service
https://01.org/lkp

2022-08-09 01:15:28

by Maximilian Luz

[permalink] [raw]
Subject: Re: [PATCH v5 0/6] Add spi-hid, transport for HID over SPI bus

On 8/7/22 09:58, Jarrett Schultz <[email protected]> wrote:
> Surface Duo devices use a touch digitizer that communicates to the main
> SoC via SPI and presents itself as a HID device. This patch's goal is to
> add the spi-hid transport driver to drivers/hid. The driver follows the
> publically available HID Over SPI Protocol Specification version 1.0.

As far as I can tell based on the downstream code you provide in [1],
your proposed driver (and v1.0 spec) is incompatible with the version
used on current Surface devices (Surface Duo 1 and 2, Surface Pro X, and
I assume some AMD/x86 based devices on which you also use spi-hid for the
digitizer). On those, SPI_HID_SUPPORTED_VERSION is 0x0100 whereas it is
0x0300 for the driver proposed here, along with at least some protocol
struct changes.

Do you have any plans on supporting those devices (i.e. protocol version
0x0100) at some point?

Regards,
Max

[1]: https://github.com/microsoft/surface-duo-oss-kernel.msm-5..4/tree/surfaceduo2/11/2022.108.8/drivers/hid/spi-hid