Files
x1e-nixos/kernel/modules/spi-hid/spi-hid-core.c
T
2026-04-10 15:45:53 +04:00

1080 lines
26 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* HID over SPI (HIDSPI v3) transport driver for QSPI touchpads.
*
* Based on Microsoft's spi-hid v2 driver.
* Copyright (c) 2020 Microsoft Corporation
*/
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/wait.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/pm_runtime.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/hid.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/regulator/consumer.h>
#include <linux/workqueue.h>
#include "spi-hid-core.h"
static struct hid_ll_driver spi_hid_ll_driver;
static int spi_hid_process_input_report(struct spi_hid *shid,
const u8 *body, int body_len);
/* QSPI transfer helpers */
static void qspi_fill_cmd(u8 *buf, u8 opcode, u32 addr)
{
buf[0] = opcode;
buf[1] = (addr >> 16) & 0xFF;
buf[2] = (addr >> 8) & 0xFF;
buf[3] = addr & 0xFF;
}
static int qspi_read_sync(struct spi_hid *shid, u32 addr, void *buf, int len)
{
struct spi_transfer xfer = {};
u8 *tx, *rx;
int ret;
tx = kzalloc(len, GFP_KERNEL);
rx = kzalloc(len, GFP_KERNEL);
if (!tx || !rx) {
kfree(tx);
kfree(rx);
return -ENOMEM;
}
qspi_fill_cmd(tx, SPI_HID_QSPI_READ_OPCODE, addr);
xfer.tx_buf = tx;
xfer.rx_buf = rx;
xfer.len = len;
xfer.tx_nbits = 4;
xfer.rx_nbits = 4;
ret = spi_sync_transfer(shid->spi, &xfer, 1);
if (ret == 0)
memcpy(buf, rx, len);
kfree(tx);
kfree(rx);
return ret;
}
static int qspi_write_sync(struct spi_hid *shid, u32 addr,
const void *data, int len)
{
struct device *dev = &shid->spi->dev;
struct spi_transfer xfer = {};
int total = SPI_HID_QSPI_CMD_LEN + len;
u8 *tx;
int ret;
tx = kzalloc(total, GFP_KERNEL);
if (!tx)
return -ENOMEM;
qspi_fill_cmd(tx, SPI_HID_QSPI_WRITE_OPCODE, addr);
if (data && len > 0)
memcpy(tx + SPI_HID_QSPI_CMD_LEN, data, len);
xfer.tx_buf = tx;
xfer.rx_buf = NULL;
xfer.len = total;
xfer.tx_nbits = 4;
xfer.rx_nbits = 4;
ret = spi_sync_transfer(shid->spi, &xfer, 1);
kfree(tx);
return ret;
}
static void spi_hid_parse_input_header(const u8 *buf,
struct spi_hid_input_header *hdr)
{
u32 raw = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
hdr->sync_const = (raw >> 24) & 0xFF;
hdr->version = raw & 0x0F;
hdr->body_len = ((raw >> 8) & 0x3FFF) * 4;
hdr->last_frag = (raw >> 22) & 1;
}
static void spi_hid_parse_body_header(const u8 *buf,
struct spi_hid_body_header *bhdr)
{
bhdr->report_type = buf[0];
bhdr->content_length = buf[1] | (buf[2] << 8);
bhdr->content_id = buf[3];
}
static int spi_hid_validate_header(struct spi_hid *shid,
struct spi_hid_input_header *hdr)
{
struct device *dev = &shid->spi->dev;
if (hdr->sync_const != SPI_HID_INPUT_HEADER_SYNC_BYTE) {
dev_err(dev, "Bad sync: 0x%02x\n", hdr->sync_const);
return -EINVAL;
}
if (hdr->version != SPI_HID_INPUT_HEADER_VERSION) {
dev_err(dev, "Bad version: %d\n", hdr->version);
return -EINVAL;
}
if (hdr->body_len == 0)
return 0;
if (shid->desc.max_input_length != 0 &&
hdr->body_len > shid->desc.max_input_length) {
dev_err(dev, "Body %u > max %u\n",
hdr->body_len, shid->desc.max_input_length);
return -EMSGSIZE;
}
return 0;
}
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);
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->flags = le16_to_cpu(raw->wFlags);
}
static int spi_hid_send_output(struct spi_hid *shid,
u8 report_type, u8 content_id,
const u8 *content, int content_len)
{
u8 body[SPI_HID_BODY_HEADER_LEN + 512];
int body_len = SPI_HID_BODY_HEADER_LEN + content_len;
if (body_len > sizeof(body)) {
dev_err(&shid->spi->dev, "Output too large: %d\n", body_len);
return -E2BIG;
}
body[0] = report_type;
body[1] = content_len & 0xFF;
body[2] = (content_len >> 8) & 0xFF;
body[3] = content_id;
if (content && content_len > 0)
memcpy(body + SPI_HID_BODY_HEADER_LEN, content, content_len);
return qspi_write_sync(shid, SPI_HID_OUTPUT_ADDR, body, body_len);
}
/* Caller must hold shid->lock. */
static int spi_hid_sync_request_ms(struct spi_hid *shid,
u8 report_type, u8 content_id,
const u8 *content, int content_len,
unsigned int timeout_ms)
{
struct device *dev = &shid->spi->dev;
unsigned long timeout;
int ret;
reinit_completion(&shid->output_done);
ret = spi_hid_send_output(shid, report_type, content_id,
content, content_len);
if (ret) {
dev_err(dev, "Failed to send output type %d: %d\n",
report_type, ret);
return ret;
}
timeout = wait_for_completion_timeout(&shid->output_done,
msecs_to_jiffies(timeout_ms));
if (timeout == 0) {
dev_err(dev, "Response timeout for type %d (%ums)\n",
report_type, timeout_ms);
return -ETIMEDOUT;
}
return 0;
}
static int spi_hid_sync_request(struct spi_hid *shid,
u8 report_type, u8 content_id,
const u8 *content, int content_len)
{
return spi_hid_sync_request_ms(shid, report_type, content_id,
content, content_len,
SPI_HID_RESPONSE_TIMEOUT_MS);
}
static int spi_hid_power_down(struct spi_hid *shid)
{
int ret;
if (!shid->powered)
return 0;
if (shid->pinctrl_sleep)
pinctrl_select_state(shid->pinctrl, shid->pinctrl_sleep);
if (shid->supply) {
ret = regulator_disable(shid->supply);
if (ret) {
dev_err(&shid->spi->dev, "regulator disable failed\n");
return ret;
}
}
shid->powered = false;
return 0;
}
static int spi_hid_power_up(struct spi_hid *shid)
{
int ret;
if (shid->powered)
return 0;
shid->powered = true;
if (shid->supply) {
ret = regulator_enable(shid->supply);
if (ret) {
shid->powered = false;
return ret;
}
}
usleep_range(5000, 6000);
return 0;
}
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 int spi_hid_error_handler(struct spi_hid *shid)
{
struct device *dev = &shid->spi->dev;
int ret;
if (shid->power_state == SPI_HID_POWER_MODE_OFF)
return 0;
dev_err(dev, "Error handler (attempt %d)\n", shid->attempts);
if (shid->attempts++ >= SPI_HID_MAX_RESET_ATTEMPTS) {
dev_err(dev, "Unresponsive device, aborting\n");
spi_hid_stop_hid(shid);
spi_hid_power_down(shid);
return -ESHUTDOWN;
}
shid->ready = false;
ret = pinctrl_select_state(shid->pinctrl, shid->pinctrl_reset);
if (ret) {
dev_err(dev, "Reset assert failed\n");
return ret;
}
shid->power_state = SPI_HID_POWER_MODE_OFF;
msleep(SPI_HID_RESET_ASSERT_MS);
shid->power_state = SPI_HID_POWER_MODE_ACTIVE;
ret = pinctrl_select_state(shid->pinctrl, shid->pinctrl_active);
if (ret) {
dev_err(dev, "Reset deassert failed\n");
return ret;
}
msleep(SPI_HID_POST_DIR_DELAY_MS);
return 0;
}
static int spi_hid_input_report_handler(struct spi_hid *shid,
const u8 *body, int body_len)
{
struct device *dev = &shid->spi->dev;
struct spi_hid_body_header bhdr;
int ret;
if (!shid->ready || !shid->hid)
return 0;
spi_hid_parse_body_header(body, &bhdr);
ret = hid_input_report(shid->hid, HID_INPUT_REPORT,
(u8 *)(body + 3),
bhdr.content_length + 1, 1);
if (ret == -ENODEV || ret == -EBUSY)
return 0;
return ret;
}
static int spi_hid_process_input_report(struct spi_hid *shid,
const u8 *body, int body_len)
{
struct device *dev = &shid->spi->dev;
struct spi_hid_body_header bhdr;
struct spi_hid_device_desc_raw *raw;
if (body_len < SPI_HID_BODY_HEADER_LEN) {
dev_err(dev, "Body too short: %d\n", body_len);
return -EINVAL;
}
spi_hid_parse_body_header(body, &bhdr);
switch (bhdr.report_type) {
case SPI_HID_REPORT_TYPE_DATA:
return spi_hid_input_report_handler(shid, body, body_len);
case SPI_HID_REPORT_TYPE_RESET_RESP:
shid->resp_type = SPI_HID_REPORT_TYPE_RESET_RESP;
shid->resp_len = 0;
if (!completion_done(&shid->output_done)) {
complete(&shid->output_done);
} else {
if (!shid->ready)
schedule_work(&shid->reset_work);
else
schedule_work(&shid->refresh_device_work);
}
return 0;
case SPI_HID_REPORT_TYPE_DEVICE_DESC:
shid->attempts = 0;
if (body_len >= SPI_HID_BODY_HEADER_LEN +
sizeof(struct spi_hid_device_desc_raw)) {
raw = (struct spi_hid_device_desc_raw *)
(body + SPI_HID_BODY_HEADER_LEN);
spi_hid_parse_dev_desc(raw, &shid->desc);
}
if (shid->resp_buf && body_len <= SPI_HID_MAX_INPUT_LEN) {
memcpy(shid->resp_buf, body, body_len);
shid->resp_len = body_len;
shid->resp_type = bhdr.report_type;
}
if (!completion_done(&shid->output_done))
complete(&shid->output_done);
else if (!shid->hid)
schedule_work(&shid->create_device_work);
else
schedule_work(&shid->refresh_device_work);
return 0;
case SPI_HID_REPORT_TYPE_COMMAND_RESP:
case SPI_HID_REPORT_TYPE_GET_FEATURE_RESP:
case SPI_HID_REPORT_TYPE_REPORT_DESC:
case SPI_HID_REPORT_TYPE_SET_FEATURE_RESP:
case SPI_HID_REPORT_TYPE_OUTPUT_REPORT_RESP:
case SPI_HID_REPORT_TYPE_GET_INPUT_RESP:
if (shid->resp_buf && body_len <= SPI_HID_MAX_INPUT_LEN) {
memcpy(shid->resp_buf, body, body_len);
shid->resp_len = body_len;
shid->resp_type = bhdr.report_type;
}
if (!completion_done(&shid->output_done))
complete(&shid->output_done);
return 0;
default:
dev_err(dev, "Unknown report type: 0x%x (body_len=%d raw: %*ph)\n",
bhdr.report_type, body_len,
min(body_len, 16), body);
return -EINVAL;
}
}
static irqreturn_t spi_hid_irq_thread(int irq, void *_shid)
{
struct spi_hid *shid = _shid;
struct device *dev = &shid->spi->dev;
struct spi_hid_input_header hdr;
struct spi_transfer xfer = {};
int ret;
if (!shid->powered)
return IRQ_HANDLED;
xfer.tx_buf = shid->irq_hdr_tx;
xfer.rx_buf = shid->irq_hdr_rx;
xfer.len = SPI_HID_INPUT_HEADER_LEN;
xfer.tx_nbits = 4;
xfer.rx_nbits = 4;
ret = spi_sync_transfer(shid->spi, &xfer, 1);
if (ret) {
shid->bus_error_count++;
shid->bus_last_error = ret;
if (shid->bus_error_count <= 5)
dev_err(dev, "Header read failed: %d\n", ret);
if (shid->bus_error_count == 100) {
dev_err(dev, "Too many read errors, disabling IRQ\n");
disable_irq_nosync(shid->spi->irq);
}
return IRQ_HANDLED;
}
spi_hid_parse_input_header(shid->irq_hdr_rx, &hdr);
ret = spi_hid_validate_header(shid, &hdr);
if (ret) {
shid->bus_error_count++;
shid->bus_last_error = ret;
if (shid->bus_error_count <= 5 ||
shid->bus_error_count % 1000 == 0)
dev_err(dev, "Invalid header (%u errors): %*ph\n",
shid->bus_error_count,
SPI_HID_INPUT_HEADER_LEN, shid->irq_hdr_rx);
if (shid->bus_error_count == 100) {
dev_err(dev, "Too many errors, disabling IRQ\n");
disable_irq_nosync(shid->spi->irq);
}
return IRQ_HANDLED;
}
shid->bus_error_count = 0;
if (hdr.body_len == 0)
return IRQ_HANDLED;
if (hdr.body_len > SPI_HID_MAX_INPUT_LEN) {
dev_err(dev, "Body too large: %u\n", hdr.body_len);
return IRQ_HANDLED;
}
memset(&xfer, 0, sizeof(xfer));
xfer.tx_buf = shid->irq_bdy_tx;
xfer.rx_buf = shid->irq_bdy_rx;
xfer.len = hdr.body_len;
xfer.tx_nbits = 4;
xfer.rx_nbits = 4;
ret = spi_sync_transfer(shid->spi, &xfer, 1);
if (ret) {
dev_err(dev, "Body read failed: %d\n", ret);
shid->bus_error_count++;
shid->bus_last_error = ret;
return IRQ_HANDLED;
}
spi_hid_process_input_report(shid, shid->irq_bdy_rx, hdr.body_len);
return IRQ_HANDLED;
}
static int spi_hid_create_device(struct spi_hid *shid);
static void spi_hid_reset_work(struct work_struct *work)
{
struct spi_hid *shid = container_of(work, struct spi_hid, reset_work);
struct device *dev = &shid->spi->dev;
int ret, attempt;
if (shid->ready)
shid->dir_count++;
flush_work(&shid->create_device_work);
if (shid->power_state == SPI_HID_POWER_MODE_OFF)
return;
flush_work(&shid->refresh_device_work);
mutex_lock(&shid->lock);
for (attempt = 0; attempt < SPI_HID_MAX_RESET_ATTEMPTS; attempt++) {
int dd_try, rd_try, rd_len;
if (attempt > 0) {
pinctrl_select_state(shid->pinctrl, shid->pinctrl_reset);
msleep(SPI_HID_RESET_ASSERT_MS);
pinctrl_select_state(shid->pinctrl, shid->pinctrl_active);
msleep(2000);
}
for (dd_try = 0; dd_try < SPI_HID_MAX_INIT_RETRIES; dd_try++) {
ret = spi_hid_sync_request(shid,
SPI_HID_OUT_DEVICE_DESC,
0, NULL, 0);
if (ret) {
dev_err(dev, "GET_DD failed: %d\n", ret);
break;
}
if (shid->resp_type == SPI_HID_REPORT_TYPE_DEVICE_DESC)
break;
if (shid->resp_type == SPI_HID_REPORT_TYPE_RESET_RESP) {
msleep(SPI_HID_POST_DIR_DELAY_MS);
continue;
}
dev_err(dev, "GET_DD: unexpected type %d\n",
shid->resp_type);
msleep(SPI_HID_POST_DIR_DELAY_MS);
}
if (dd_try >= SPI_HID_MAX_INIT_RETRIES || ret) {
dev_err(dev, "GET_DD failed, restarting\n");
continue;
}
for (rd_try = 0; rd_try < SPI_HID_MAX_INIT_RETRIES; rd_try++) {
ret = spi_hid_sync_request(shid,
SPI_HID_OUT_REPORT_DESC,
0, NULL, 0);
if (ret) {
dev_err(dev, "GET_RD failed: %d\n", ret);
break;
}
if (shid->resp_type == SPI_HID_REPORT_TYPE_REPORT_DESC)
break;
if (shid->resp_type == SPI_HID_REPORT_TYPE_RESET_RESP) {
msleep(SPI_HID_POST_DIR_DELAY_MS);
continue;
}
dev_err(dev, "GET_RD: unexpected type %d\n",
shid->resp_type);
msleep(SPI_HID_POST_DIR_DELAY_MS);
}
if (rd_try >= SPI_HID_MAX_INIT_RETRIES || ret) {
dev_err(dev, "GET_RD failed, restarting\n");
continue;
}
rd_len = shid->resp_len - SPI_HID_BODY_HEADER_LEN;
if (rd_len <= 0 || rd_len > 2048) {
dev_err(dev, "GET_RD: bad length %d\n", rd_len);
continue;
}
memcpy(shid->rd_buf,
shid->resp_buf + SPI_HID_BODY_HEADER_LEN, rd_len);
shid->rd_len = rd_len;
goto success;
}
dev_err(dev, "Init failed after %d reset cycles\n", attempt);
mutex_unlock(&shid->lock);
spi_hid_error_handler(shid);
return;
success:
mutex_unlock(&shid->lock);
if (!shid->hid)
spi_hid_create_device(shid);
}
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));
return PTR_ERR(hid);
}
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);
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;
int ret;
if (shid->desc.hid_version != SPI_HID_SUPPORTED_VERSION) {
dev_err(dev, "Unsupported version 0x%04x (expected 0x%04x)\n",
shid->desc.hid_version, SPI_HID_SUPPORTED_VERSION);
spi_hid_error_handler(shid);
return;
}
ret = spi_hid_create_device(shid);
if (ret) {
dev_err(dev, "Failed to create HID device: %d\n", ret);
return;
}
shid->attempts = 0;
}
static void spi_hid_refresh_device_work(struct work_struct *work)
{
struct spi_hid *shid =
container_of(work, struct spi_hid, refresh_device_work);
shid->ready = true;
}
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(&spi->dev, "max_input_length %d < HID_MIN_BUFFER_SIZE\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)
{
return 0;
}
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);
if (!shid->hid)
return -ENODEV;
return 0;
}
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;
if (!shid->rd_buf || shid->rd_len <= 0) {
dev_err(dev, "No report descriptor available\n");
return -ENODATA;
}
ret = hid_parse_report(hid, shid->rd_buf, shid->rd_len);
if (ret)
dev_err(dev, "hid_parse_report failed: %d\n", ret);
else
shid->ready = true;
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, copy_len;
if (reqtype == HID_REQ_SET_REPORT && rtype == HID_FEATURE_REPORT) {
mutex_lock(&shid->lock);
ret = spi_hid_sync_request(shid, SPI_HID_OUT_SET_FEATURE,
buf[0], buf + 1, len - 1);
mutex_unlock(&shid->lock);
if (ret)
return ret;
if (shid->resp_type == SPI_HID_REPORT_TYPE_RESET_RESP)
return len;
if (shid->resp_type != SPI_HID_REPORT_TYPE_SET_FEATURE_RESP) {
dev_err(dev, "SET_FEATURE got resp type %d\n",
shid->resp_type);
return -EIO;
}
return len;
}
if (reqtype == HID_REQ_GET_REPORT && rtype == HID_FEATURE_REPORT) {
mutex_lock(&shid->lock);
ret = spi_hid_sync_request(shid, SPI_HID_OUT_GET_FEATURE,
reportnum, NULL, 0);
mutex_unlock(&shid->lock);
if (ret) {
dev_err(dev, "GET_FEATURE 0x%02x failed: %d\n",
reportnum, ret);
return ret;
}
if (shid->resp_type == SPI_HID_REPORT_TYPE_RESET_RESP) {
memset(buf, 0, len);
buf[0] = reportnum;
return len;
}
if (shid->resp_type != SPI_HID_REPORT_TYPE_GET_FEATURE_RESP) {
dev_err(dev, "GET_FEATURE got resp type %d\n",
shid->resp_type);
return -EIO;
}
copy_len = shid->resp_len - SPI_HID_BODY_HEADER_LEN;
if (copy_len <= 0) {
dev_err(dev, "GET_FEATURE 0x%02x: empty response\n",
reportnum);
return -EIO;
}
if (copy_len > (int)len - 1)
copy_len = (int)len - 1;
buf[0] = reportnum;
memcpy(buf + 1, shid->resp_buf + SPI_HID_BODY_HEADER_LEN,
copy_len);
return 1 + copy_len;
}
if (reqtype == HID_REQ_SET_REPORT) {
mutex_lock(&shid->lock);
ret = spi_hid_send_output(shid, SPI_HID_OUT_OUTPUT_REPORT,
buf[0], buf + 1, len - 1);
mutex_unlock(&shid->lock);
return ret ? ret : len;
}
dev_err(dev, "Unsupported request: reqtype=%d rtype=%d\n",
reqtype, rtype);
return -EIO;
}
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 device *dev = &spi->dev;
int ret;
mutex_lock(&shid->lock);
if (!shid->ready) {
dev_err(dev, "output_report called in unready state\n");
ret = -ENODEV;
goto out;
}
ret = spi_hid_send_output(shid, SPI_HID_OUT_OUTPUT_REPORT,
buf[0], &buf[1], len - 1);
if (ret == 0)
ret = len;
out:
mutex_unlock(&shid->lock);
return ret;
}
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,
};
static ssize_t ready_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spi_hid *shid = dev_get_drvdata(dev);
return sysfs_emit(buf, "%s\n", shid->ready ? "ready" : "not ready");
}
static DEVICE_ATTR_RO(ready);
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 sysfs_emit(buf, "%d (%d)\n",
shid->bus_error_count, shid->bus_last_error);
}
static DEVICE_ATTR_RO(bus_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 sysfs_emit(buf, "%d\n", shid->dir_count);
}
static DEVICE_ATTR_RO(device_initiated_reset_count);
static const struct attribute *const spi_hid_attributes[] = {
&dev_attr_ready.attr,
&dev_attr_bus_error_count.attr,
&dev_attr_device_initiated_reset_count.attr,
NULL
};
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");
return spi->irq ?: -EINVAL;
}
shid = devm_kzalloc(dev, sizeof(*shid), GFP_KERNEL);
if (!shid)
return -ENOMEM;
shid->spi = spi;
shid->power_state = SPI_HID_POWER_MODE_ACTIVE;
spi_set_drvdata(spi, shid);
spi->mode = SPI_MODE_0 | SPI_TX_QUAD | SPI_RX_QUAD;
spi->max_speed_hz = 20000000;
spi->bits_per_word = 8;
ret = spi_setup(spi);
if (ret) {
dev_err(dev, "SPI setup failed: %d\n", ret);
return ret;
}
shid->resp_buf = kzalloc(SPI_HID_MAX_INPUT_LEN, GFP_KERNEL);
shid->rd_buf = kzalloc(2048, GFP_KERNEL);
shid->irq_hdr_tx = kzalloc(SPI_HID_INPUT_HEADER_LEN, GFP_KERNEL);
shid->irq_hdr_rx = kzalloc(SPI_HID_INPUT_HEADER_LEN, GFP_KERNEL);
shid->irq_bdy_tx = kzalloc(SPI_HID_MAX_INPUT_LEN, GFP_KERNEL);
shid->irq_bdy_rx = kzalloc(SPI_HID_MAX_INPUT_LEN, GFP_KERNEL);
if (!shid->resp_buf || !shid->rd_buf ||
!shid->irq_hdr_tx || !shid->irq_hdr_rx ||
!shid->irq_bdy_tx || !shid->irq_bdy_rx) {
ret = -ENOMEM;
goto err_free;
}
qspi_fill_cmd(shid->irq_hdr_tx, SPI_HID_QSPI_READ_OPCODE,
SPI_HID_INPUT_HDR_ADDR);
qspi_fill_cmd(shid->irq_bdy_tx, SPI_HID_QSPI_READ_OPCODE,
SPI_HID_INPUT_BDY_ADDR);
shid->desc.max_input_length = SPI_HID_MAX_INPUT_LEN;
ret = sysfs_create_files(&dev->kobj, spi_hid_attributes);
if (ret) {
dev_err(dev, "sysfs_create_files failed\n");
goto err_free;
}
mutex_init(&shid->lock);
init_completion(&shid->output_done);
complete(&shid->output_done);
INIT_WORK(&shid->reset_work, spi_hid_reset_work);
INIT_WORK(&shid->create_device_work, spi_hid_create_device_work);
INIT_WORK(&shid->refresh_device_work, spi_hid_refresh_device_work);
shid->supply = devm_regulator_get_optional(dev, "vdd");
if (IS_ERR(shid->supply)) {
if (PTR_ERR(shid->supply) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER;
goto err_sysfs;
}
shid->supply = NULL;
}
shid->pinctrl = devm_pinctrl_get(dev);
if (IS_ERR(shid->pinctrl)) {
dev_err(dev, "pinctrl_get failed: %ld\n",
PTR_ERR(shid->pinctrl));
ret = PTR_ERR(shid->pinctrl);
goto err_sysfs;
}
shid->pinctrl_reset = pinctrl_lookup_state(shid->pinctrl, "reset");
if (IS_ERR(shid->pinctrl_reset)) {
dev_err(dev, "pinctrl 'reset' not found: %ld\n",
PTR_ERR(shid->pinctrl_reset));
ret = PTR_ERR(shid->pinctrl_reset);
goto err_sysfs;
}
shid->pinctrl_active = pinctrl_lookup_state(shid->pinctrl, "active");
if (IS_ERR(shid->pinctrl_active)) {
dev_err(dev, "pinctrl 'active' not found: %ld\n",
PTR_ERR(shid->pinctrl_active));
ret = PTR_ERR(shid->pinctrl_active);
goto err_sysfs;
}
shid->pinctrl_sleep = pinctrl_lookup_state(shid->pinctrl, "sleep");
if (IS_ERR(shid->pinctrl_sleep))
shid->pinctrl_sleep = shid->pinctrl_reset;
irqflags = irq_get_trigger_type(spi->irq) | IRQF_ONESHOT;
ret = request_threaded_irq(spi->irq, NULL, spi_hid_irq_thread,
irqflags, dev_name(&spi->dev), shid);
if (ret) {
dev_err(dev, "request_threaded_irq failed: %d\n", ret);
goto err_sysfs;
}
disable_irq(spi->irq);
shid->irq_enabled = false;
pm_runtime_enable(dev->parent);
ret = pm_runtime_get_sync(dev->parent);
if (ret < 0) {
dev_warn(dev, "SPI ctrl PM get failed: %d\n", ret);
pm_runtime_put_noidle(dev->parent);
}
/*
* Bring-up: hold the device in reset (power off, reset asserted)
* for a few ms, then deassert reset and enable the regulator. The
* 2 s settle gives the touchpad firmware time to boot before we
* arm the IRQ and start talking to it.
*/
pinctrl_select_state(shid->pinctrl, shid->pinctrl_reset);
msleep(SPI_HID_RESET_ASSERT_MS);
pinctrl_select_state(shid->pinctrl, shid->pinctrl_active);
spi_hid_power_up(shid);
msleep(2000);
pm_runtime_put(dev->parent);
enable_irq(spi->irq);
shid->irq_enabled = true;
return 0;
err_sysfs:
sysfs_remove_files(&dev->kobj, spi_hid_attributes);
err_free:
kfree(shid->resp_buf);
kfree(shid->rd_buf);
kfree(shid->irq_hdr_tx);
kfree(shid->irq_hdr_rx);
kfree(shid->irq_bdy_tx);
kfree(shid->irq_bdy_rx);
return ret;
}
static void spi_hid_remove(struct spi_device *spi)
{
struct spi_hid *shid = spi_get_drvdata(spi);
struct device *dev = &spi->dev;
spi_hid_power_down(shid);
free_irq(spi->irq, shid);
shid->irq_enabled = false;
sysfs_remove_files(&dev->kobj, spi_hid_attributes);
spi_hid_stop_hid(shid);
kfree(shid->resp_buf);
kfree(shid->rd_buf);
kfree(shid->irq_hdr_tx);
kfree(shid->irq_hdr_rx);
kfree(shid->irq_bdy_tx);
kfree(shid->irq_bdy_rx);
}
static const struct of_device_id spi_hid_of_match[] = {
{ .compatible = "hid-over-spi" },
{},
};
MODULE_DEVICE_TABLE(of, spi_hid_of_match);
static const struct spi_device_id spi_hid_id_table[] = {
{ "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 = spi_hid_probe,
.remove = spi_hid_remove,
.id_table = spi_hid_id_table,
};
module_spi_driver(spi_hid_driver);
MODULE_DESCRIPTION("HID over SPI (HIDSPI v3) QSPI transport driver");
MODULE_LICENSE("GPL");