[RFC PATCH 2/6] HID: spi-hid: add spi-mem quad-SPI transfers

From: fQwQf

Date: Sat Oct 10 2026 - 13:01:30 EST


Use explicit opcode, address, dummy and data phases for the 1-4-4 reads
and 1-4-4 writes used by the Romulus touchpad. Four quad dummy bytes
provide the eight read dummy clocks. The output data phase excludes the
opcode and address already supplied to spi-mem.

Register the frontends through spi-mem so controllers exposing memory
operations can execute the transactions. Single-lane devices retain
their ordinary spi_sync/spi_write transfers. Reject bus-width
combinations this transport cannot encode.

Only quad writes require the four-byte opcode/address prefix; preserve
the existing short-write behavior for single-lane SPI.

Controllers do not need mem_ops to bind these frontends. Single-lane
transfers bypass spi_mem_exec_op(); for supported memory operations,
spi-mem can fall back to ordinary SPI messages when mem_ops are absent.
The new SPI_MEM Kconfig dependency remains required.

Signed-off-by: Jizhou Tong <fqwqf@xxxxxxxxx>
---
drivers/hid/spi-hid/Kconfig | 2 +-
drivers/hid/spi-hid/spi-hid-acpi.c | 28 ++++++-----
drivers/hid/spi-hid/spi-hid-core.c | 80 ++++++++++++++++++++++--------
drivers/hid/spi-hid/spi-hid-core.h | 1 +
drivers/hid/spi-hid/spi-hid-of.c | 35 ++++++-------
drivers/hid/spi-hid/spi-hid.h | 6 +--
6 files changed, 96 insertions(+), 56 deletions(-)

diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig
index 656dbda1c5d873e5bc6c87dda1fbe81460425d30..c84da18ab82819b019cd58a2d3eb9a7b0f6417b0 100644
--- a/drivers/hid/spi-hid/Kconfig
+++ b/drivers/hid/spi-hid/Kconfig
@@ -6,7 +6,7 @@
menuconfig SPI_HID
tristate "SPI HID support"
default y
- depends on SPI
+ depends on SPI && SPI_MEM

if SPI_HID

diff --git a/drivers/hid/spi-hid/spi-hid-acpi.c b/drivers/hid/spi-hid/spi-hid-acpi.c
index a52cafe86c052f0ab8c4761c43355969eb689157..790342073e6f53d61a193429094e965c7819aeda 100644
--- a/drivers/hid/spi-hid/spi-hid-acpi.c
+++ b/drivers/hid/spi-hid/spi-hid-acpi.c
@@ -194,8 +194,9 @@ static void spi_hid_acpi_sleep_minimal_reset_delay(struct spihid_ops *ops)
fsleep(1000 * conf->minimal_reset_delay_ms);
}

-static int spi_hid_acpi_probe(struct spi_device *spi)
+static int spi_hid_acpi_probe(struct spi_mem *mem)
{
+ struct spi_device *spi = mem->spi;
struct device *dev = &spi->dev;
struct acpi_device *adev;
struct spi_hid_acpi_config *config;
@@ -232,7 +233,7 @@ static int spi_hid_acpi_probe(struct spi_device *spi)
return error;
}

- return spi_hid_core_probe(spi, &config->ops, &config->property_conf);
+ return spi_hid_core_probe(mem, &config->ops, &config->property_conf);
}

static const struct acpi_device_id spi_hid_acpi_match[] = {
@@ -242,20 +243,21 @@ static const struct acpi_device_id spi_hid_acpi_match[] = {
};
MODULE_DEVICE_TABLE(acpi, spi_hid_acpi_match);

-static struct spi_driver spi_hid_acpi_driver = {
- .driver = {
- .name = "spi_hid_acpi",
- .owner = THIS_MODULE,
- .pm = &spi_hid_core_pm,
- .acpi_match_table = spi_hid_acpi_match,
- .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- .dev_groups = spi_hid_groups,
+static struct spi_mem_driver spi_hid_acpi_driver = {
+ .spidrv = {
+ .driver = {
+ .name = "spi_hid_acpi",
+ .pm = &spi_hid_core_pm,
+ .acpi_match_table = spi_hid_acpi_match,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .dev_groups = spi_hid_groups,
+ },
},
- .probe = spi_hid_acpi_probe,
- .remove = spi_hid_core_remove,
+ .probe = spi_hid_acpi_probe,
+ .remove = spi_hid_core_remove,
};

-module_spi_driver(spi_hid_acpi_driver);
+module_spi_mem_driver(spi_hid_acpi_driver);

MODULE_DESCRIPTION("HID over SPI ACPI transport driver");
MODULE_AUTHOR("Angela Czubak <aczubak@xxxxxxxxxx>");
diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-hid-core.c
index 65a1cd2c72e8025f50a188e17afd2fa5b359abb1..9d02c156fcd641e9c944c0dfe08b5aad57a97f6a 100644
--- a/drivers/hid/spi-hid/spi-hid-core.c
+++ b/drivers/hid/spi-hid/spi-hid-core.c
@@ -216,7 +216,20 @@ static int spi_hid_input_sync(struct spi_hid *shid, void *buf, u16 length,
shid->input_transfer[0].len,
shid->input_transfer[1].rx_buf, 0, 0);

- error = spi_sync(shid->spi, &shid->input_message);
+ if (shid->spi->mode & SPI_RX_QUAD) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(shid->conf->read_opcode, 1),
+ SPI_MEM_OP_ADDR(3, is_header ?
+ shid->conf->input_report_header_address :
+ shid->conf->input_report_body_address, 4),
+ SPI_MEM_OP_DUMMY(4, 4),
+ SPI_MEM_OP_DATA_IN(length, rx, 4));
+
+ error = spi_mem_exec_op(shid->mem, &op);
+ shid->input_message.status = error;
+ } else {
+ error = spi_sync(shid->spi, &shid->input_message);
+ }
if (error) {
dev_err(&shid->spi->dev, "Error starting sync transfer: %d\n", error);
shid->bus_error_count++;
@@ -232,11 +245,24 @@ static int spi_hid_output(struct spi_hid *shid, const void *buf, u16 length)
{
int error;

- if (length > shid->xfer_size)
+ if (length > shid->xfer_size ||
+ ((shid->spi->mode & SPI_TX_QUAD) && length < 4))
return -EMSGSIZE;

- memcpy(shid->xfer_buf, buf, length);
- error = spi_write(shid->spi, shid->xfer_buf, length);
+ if (shid->spi->mode & SPI_TX_QUAD) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(shid->conf->write_opcode, 1),
+ SPI_MEM_OP_ADDR(3, shid->conf->output_report_address, 4),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(length - 4, shid->xfer_buf, 4));
+
+ /* spi-mem supplies the opcode and address outside the data phase. */
+ memcpy(shid->xfer_buf, (const u8 *)buf + 4, length - 4);
+ error = spi_mem_exec_op(shid->mem, &op);
+ } else {
+ memcpy(shid->xfer_buf, buf, length);
+ error = spi_write(shid->spi, shid->xfer_buf, length);
+ }

if (error) {
shid->bus_error_count++;
@@ -1287,7 +1313,7 @@ static void spi_hid_find_max_report(struct hid_device *hid, u32 type,
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);
+ struct spi_hid *shid = spi_mem_get_drvdata(spi_get_drvdata(spi));
int error = 0;
/*
* HID_MIN_BUFFER_SIZE is the minimum transport buffer size, not a
@@ -1319,7 +1345,7 @@ static void spi_hid_ll_stop(struct hid_device *hid)
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 spi_hid *shid = spi_mem_get_drvdata(spi_get_drvdata(spi));

set_bit(SPI_HID_STARTED, &shid->flags);
return 0;
@@ -1328,7 +1354,7 @@ static int spi_hid_ll_open(struct hid_device *hid)
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 spi_hid *shid = spi_mem_get_drvdata(spi_get_drvdata(spi));

clear_bit(SPI_HID_STARTED, &shid->flags);
shid->reset_attempts = 0;
@@ -1337,7 +1363,7 @@ static void spi_hid_ll_close(struct hid_device *hid)
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);
+ struct spi_hid *shid = spi_mem_get_drvdata(spi_get_drvdata(spi));
int error = 0;

guard(mutex)(&shid->output_lock);
@@ -1350,7 +1376,7 @@ static int spi_hid_ll_power(struct hid_device *hid, int level)
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 spi_hid *shid = spi_mem_get_drvdata(spi_get_drvdata(spi));
struct device *dev = &spi->dev;
unsigned int rsize = shid->desc.report_descriptor_length;
int error, len;
@@ -1395,7 +1421,7 @@ static int spi_hid_ll_output_report(struct hid_device *hid, __u8 *buf,
size_t len)
{
struct spi_device *spi = hid->driver_data;
- struct spi_hid *shid = spi_get_drvdata(spi);
+ struct spi_hid *shid = spi_mem_get_drvdata(spi_get_drvdata(spi));
struct device *dev = &spi->dev;
struct spi_hid_output_report report = {
.report_type = OUTPUT_REPORT,
@@ -1428,7 +1454,7 @@ static int spi_hid_ll_raw_request(struct hid_device *hid,
size_t len, unsigned char rtype, int reqtype)
{
struct spi_device *spi = hid->driver_data;
- struct spi_hid *shid = spi_get_drvdata(spi);
+ struct spi_hid *shid = spi_mem_get_drvdata(spi_get_drvdata(spi));
struct device *dev = &spi->dev;
int ret;

@@ -1490,7 +1516,7 @@ static struct hid_ll_driver spi_hid_ll_driver = {
static ssize_t bus_error_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct spi_hid *shid = dev_get_drvdata(dev);
+ struct spi_hid *shid = spi_mem_get_drvdata(dev_get_drvdata(dev));

return sysfs_emit(buf, "%u (%d)\n",
shid->bus_error_count, shid->bus_last_error);
@@ -1501,7 +1527,7 @@ static ssize_t regulator_error_count_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
- struct spi_hid *shid = dev_get_drvdata(dev);
+ struct spi_hid *shid = spi_mem_get_drvdata(dev_get_drvdata(dev));

return sysfs_emit(buf, "%u (%d)\n",
shid->regulator_error_count,
@@ -1513,7 +1539,7 @@ 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);
+ struct spi_hid *shid = spi_mem_get_drvdata(dev_get_drvdata(dev));

return sysfs_emit(buf, "%u\n", shid->dir_count);
}
@@ -1644,9 +1670,10 @@ static int spi_hid_register_panel_follower(struct spi_hid *shid)
return drm_panel_add_follower(dev, &shid->panel_follower);
}

-int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
+int spi_hid_core_probe(struct spi_mem *mem, struct spihid_ops *ops,
struct spi_hid_conf *conf)
{
+ struct spi_device *spi = mem->spi;
struct device *dev = &spi->dev;
struct spi_hid *shid;
int error;
@@ -1654,10 +1681,20 @@ int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
if (spi->irq <= 0)
return dev_err_probe(dev, spi->irq ?: -EINVAL, "Missing IRQ\n");

+ if ((spi->mode & (SPI_RX_QUAD | SPI_TX_QUAD)) &&
+ (spi->mode & (SPI_RX_QUAD | SPI_TX_QUAD)) !=
+ (SPI_RX_QUAD | SPI_TX_QUAD))
+ return dev_err_probe(dev, -EINVAL,
+ "Quad mode needs four lanes in both directions\n");
+
+ if (spi->mode & (SPI_RX_DUAL | SPI_TX_DUAL | SPI_RX_OCTAL | SPI_TX_OCTAL))
+ return dev_err_probe(dev, -EOPNOTSUPP, "Unsupported SPI bus width\n");
+
shid = devm_kzalloc(dev, sizeof(*shid), GFP_KERNEL);
if (!shid)
return -ENOMEM;

+ shid->mem = mem;
shid->spi = spi;
shid->power_state = HIDSPI_OFF;
shid->ops = ops;
@@ -1665,7 +1702,7 @@ int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
set_bit(SPI_HID_RESET_PENDING, &shid->flags);
shid->is_panel_follower = drm_is_panel_follower(&spi->dev);

- spi_set_drvdata(spi, shid);
+ spi_mem_set_drvdata(mem, shid);

/* Using now populated conf let's pre-calculate the read approvals */
spi_hid_populate_read_approvals(shid->conf, shid->read_approval_header,
@@ -1726,9 +1763,10 @@ int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
}
EXPORT_SYMBOL_GPL(spi_hid_core_probe);

-void spi_hid_core_remove(struct spi_device *spi)
+int spi_hid_core_remove(struct spi_mem *mem)
{
- struct spi_hid *shid = spi_get_drvdata(spi);
+ struct spi_device *spi = mem->spi;
+ struct spi_hid *shid = spi_mem_get_drvdata(mem);
struct device *dev = &spi->dev;
int error;

@@ -1753,12 +1791,14 @@ void spi_hid_core_remove(struct spi_device *spi)
if (error)
dev_err(dev, "failed to disable regulator\n");
}
+
+ return 0;
}
EXPORT_SYMBOL_GPL(spi_hid_core_remove);

static int spi_hid_core_pm_suspend(struct device *dev)
{
- struct spi_hid *shid = dev_get_drvdata(dev);
+ struct spi_hid *shid = spi_mem_get_drvdata(dev_get_drvdata(dev));

if (shid->is_panel_follower)
return 0;
@@ -1768,7 +1808,7 @@ static int spi_hid_core_pm_suspend(struct device *dev)

static int spi_hid_core_pm_resume(struct device *dev)
{
- struct spi_hid *shid = dev_get_drvdata(dev);
+ struct spi_hid *shid = spi_mem_get_drvdata(dev_get_drvdata(dev));

if (shid->is_panel_follower)
return 0;
diff --git a/drivers/hid/spi-hid/spi-hid-core.h b/drivers/hid/spi-hid/spi-hid-core.h
index 9cbe2e57400443bd68056057d08f97c39c403a70..554288dd17631edc1a503755b04170c04501bcb7 100644
--- a/drivers/hid/spi-hid/spi-hid-core.h
+++ b/drivers/hid/spi-hid/spi-hid-core.h
@@ -44,6 +44,7 @@ struct spi_hid_device_descriptor {

/* Driver context */
struct spi_hid {
+ struct spi_mem *mem;
void *xfer_alloc;
u8 *xfer_buf;
size_t xfer_size;
diff --git a/drivers/hid/spi-hid/spi-hid-of.c b/drivers/hid/spi-hid/spi-hid-of.c
index 605edecc4f7ee0423c37f0960cd4974b04ab387c..51bcb1a1b2a85480044c6514890f790e2930f523 100644
--- a/drivers/hid/spi-hid/spi-hid-of.c
+++ b/drivers/hid/spi-hid/spi-hid-of.c
@@ -175,8 +175,9 @@ static void spi_hid_of_sleep_minimal_reset_delay(struct spihid_ops *ops)
fsleep(1000 * conf->timing_data->minimal_reset_delay_ms);
}

-static int spi_hid_of_probe(struct spi_device *spi)
+static int spi_hid_of_probe(struct spi_mem *mem)
{
+ struct spi_device *spi = mem->spi;
struct device *dev = &spi->dev;
struct spi_hid_of_config *config;
int error;
@@ -197,16 +198,11 @@ static int spi_hid_of_probe(struct spi_device *spi)
if (!config->timing_data)
config->timing_data = &timing_data;

- /*
- * FIXME: multi-SPI not supported. Once it is, derive the
- * HID-over-SPI flags from spi->mode.
- */
-
error = spi_hid_of_populate_config(config, dev);
if (error)
return dev_err_probe(dev, error, "Unable to populate config data\n");

- return spi_hid_core_probe(spi, &config->ops, &config->property_conf);
+ return spi_hid_core_probe(mem, &config->ops, &config->property_conf);
}

static const struct of_device_id spi_hid_of_match[] = {
@@ -222,21 +218,22 @@ static const struct spi_device_id spi_hid_of_id_table[] = {
};
MODULE_DEVICE_TABLE(spi, spi_hid_of_id_table);

-static struct spi_driver spi_hid_of_driver = {
- .driver = {
- .name = "spi_hid_of",
- .owner = THIS_MODULE,
- .pm = &spi_hid_core_pm,
- .of_match_table = spi_hid_of_match,
- .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- .dev_groups = spi_hid_groups,
+static struct spi_mem_driver spi_hid_of_driver = {
+ .spidrv = {
+ .driver = {
+ .name = "spi_hid_of",
+ .pm = &spi_hid_core_pm,
+ .of_match_table = spi_hid_of_match,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .dev_groups = spi_hid_groups,
+ },
+ .id_table = spi_hid_of_id_table,
},
- .probe = spi_hid_of_probe,
- .remove = spi_hid_core_remove,
- .id_table = spi_hid_of_id_table,
+ .probe = spi_hid_of_probe,
+ .remove = spi_hid_core_remove,
};

-module_spi_driver(spi_hid_of_driver);
+module_spi_mem_driver(spi_hid_of_driver);

MODULE_DESCRIPTION("HID over SPI OF transport driver");
MODULE_AUTHOR("Dmitry Antipov <dmanti@xxxxxxxxxxxxx>");
diff --git a/drivers/hid/spi-hid/spi-hid.h b/drivers/hid/spi-hid/spi-hid.h
index 17b2fdf192ed2cf40db0638874112c72595fdd6d..dd3a851777a22eefa9005dc6f7a18bf62c5ac6b3 100644
--- a/drivers/hid/spi-hid/spi-hid.h
+++ b/drivers/hid/spi-hid/spi-hid.h
@@ -7,7 +7,7 @@
#ifndef SPI_HID_H
#define SPI_HID_H

-#include <linux/spi/spi.h>
+#include <linux/spi/spi-mem.h>
#include <linux/sysfs.h>

/* struct spi_hid_conf - Conf provided to the core */
@@ -35,10 +35,10 @@ struct spihid_ops {
void (*sleep_minimal_reset_delay)(struct spihid_ops *ops);
};

-int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
+int spi_hid_core_probe(struct spi_mem *mem, struct spihid_ops *ops,
struct spi_hid_conf *conf);

-void spi_hid_core_remove(struct spi_device *spi);
+int spi_hid_core_remove(struct spi_mem *mem);

extern const struct attribute_group *spi_hid_groups[];
extern const struct dev_pm_ops spi_hid_core_pm;