From nobody Fri Apr 3 16:19:31 2026 Received: from mail-pl1-f171.google.com (mail-pl1-f171.google.com [209.85.214.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A112A33A9C4 for ; Thu, 2 Apr 2026 01:59:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095188; cv=none; b=KKNTk7tWePYOkwLk5UK6L9gIf81v+0gdPmbGsiXTUFaM2RLKe8Kpaad0deId6+2QWyJg73CgCXaXKqh0/aQYOgrfkYyVgmbLrgX7iot7akD/M1a7OohE9mifqxm5k6rupvuMTIuPKvs31TzGveA04PrpQ+QE6j8RA8HHG6Ou8EY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095188; c=relaxed/simple; bh=EnqLNJCTcvH0q+/xqzcK90S2pdO7Zg4AFKyEqEget/Q=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=AuR7S6Sm7GErRW8taEGsopAlxHA8xHAcy+lkQ8x14Y35Ozl6B9zGW1Kx8uwPys1w2kL2oYVdlHHVLZ+CgQLeHJYRoGss1z4+WqyW37Q6/uowpv0N5ZcYD8bhZpLR6P/VgEKQ80X3qAKPXlOk74vkkcwAF1KQQTjM3TmWFhmISYY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=c8lW/ZjC; arc=none smtp.client-ip=209.85.214.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="c8lW/ZjC" Received: by mail-pl1-f171.google.com with SMTP id d9443c01a7336-2aaf43014d0so2170105ad.2 for ; Wed, 01 Apr 2026 18:59:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095184; x=1775699984; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=O9lFXpOnmNivau0HIkBY2iJi8wwoteY30kbns1hbNbw=; b=c8lW/ZjChoqM5/pAl6z2en1cqs4ODyMR0PwUba9Cc7KvawqPTxFMsgzH9Z2OBsm3Ru X9YC23eFUCbT3w5l7KyiKT2kRaZd19h1PZ0mbndGyWeTF8Q9N7JenmQao53JGxpDUuH2 apeRvwJ45eWjssW74JnuCiBYORqfYGTxffgwQ= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095184; x=1775699984; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=O9lFXpOnmNivau0HIkBY2iJi8wwoteY30kbns1hbNbw=; b=OU6e89BWxwSHMsKTuVY9Y+v4BwuWQnAxu718uj9kPbxoRS0TaCBAIxt0ex9XqNk6+q KqpXiTLy3SbTlLDl1gPfyzb4MaPPaLXZZXkvRkYb/QZfPDIWtG2IrcLdust2+erjkrPj t01AuWxqbEq+Nxh3B54dXvHVBIW7ighFeSzVm7F3gDIT5B6ZEBjKYLZkcwmGO8xPOG6p x/6WhjLn20AdwfdtThlHR//H7OBTVhqIBGGw3JSdKKgdC49dJdp3TmfPoPdknKnRDwYL xvCQMCokUSwXyMbkAN2FEWKWj4cukK4PmZhp9Z1ZZbXfFSISGE0+DT6vxlWgw64Ectac fb7w== X-Forwarded-Encrypted: i=1; AJvYcCXn0JFw/1iDntv+DIMLgzMBJ2xms7fgeq2qVNO2y5cbv8s57iUsdskE8k1I0UWF/2Sb+Q8aBeSIIdJCk3s=@vger.kernel.org X-Gm-Message-State: AOJu0YxZy11S/lGGfo4bLzzEJ1cJ2uOV0g97xTQILOi0Kxc+HSjpGmLT Vultu1wgDXaf0lX5UciwLVgp1tI3dSmWk5D7GG/ekyQBXeKYxq9ZLq+amn2y0YgqBQ== X-Gm-Gg: ATEYQzx2Hzct6foCtbI3OygOv+p4rDgThtov5onLkeGyL4mYecn8sql3XpqWclj0sau Ch4EHOzdhsmoLPbjKOtGYMiKuDl5cbcsf9gQaqXbtjgGyOj0/yRdiXtx4wAGzHOu0BOjy5H3rE+ bN80/bBqGJqzWe0OYe2p4B28a9MXjasfuhqZLRM4UVtQlFzdIW7hLDQhLNiPPl5dAMwxTiOhETS KgJ4w0tVrNdqtrhjHzs5S5f6goaOXZSFAOOelZd3t5exKZcJgFfKcMUA9aNa5zvbD7zsw4/nzJ5 T4AgdXjVQy2iikilo5YsryR54gl4L5SDhQhqNraEGeiw8dCAB5Qz8VoEPvSQFnASqfc22KbX06b thbmrMHMM8CKzfSkhy42RwQZIXq88XhstwqJ6eg+QaKVbrlPurx4E3pLAH9obDcBWP/7udENqT7 mkob2/G6sexbNuwVxsmpClxjubb+EtNuf+VktG02pBgSBFcUVZLiB0AY6H+3BPkm5JYmizpLZiN y7+s+v73OA/fFBegBea68t1LY0/zaBWjQ== X-Received: by 2002:a17:902:e851:b0:2ae:b9cd:d2ce with SMTP id d9443c01a7336-2b275b4a227mr15040575ad.27.1775095183891; Wed, 01 Apr 2026 18:59:43 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:43 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:41 +0000 Subject: [PATCH v3 04/11] HID: spi-hid: add spi-hid driver HID layer Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260402-send-upstream-v3-4-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang , Dmitry Antipov , Angela Czubak X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1775095180; l=16407; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=EnqLNJCTcvH0q+/xqzcK90S2pdO7Zg4AFKyEqEget/Q=; b=OP0zkCq23IY9fMPPmA1mIvs+C4YYM+3vU4P6rzGd4BPoFUAyIo1CqlScI5LFcrtDmj3CJFOhv RYlLuw3ZFX2BS1Ish9HjYsYS0YizCZxemcOuXXsCIRdVlQ7Ap5YBkrI X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= Add HID low level driver callbacks to register SPI as a HID driver, and an external touch device as a HID device. Signed-off-by: Dmitry Antipov Signed-off-by: Angela Czubak Signed-off-by: Jingyuan Liang --- drivers/hid/spi-hid/spi-hid-core.c | 519 +++++++++++++++++++++++++++++++++= ++++ 1 file changed, 519 insertions(+) diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-h= id-core.c index d7b4d4adad95..4723b87346d4 100644 --- a/drivers/hid/spi-hid/spi-hid-core.c +++ b/drivers/hid/spi-hid/spi-hid-core.c @@ -20,13 +20,69 @@ * Copyright (c) 2006-2010 Jiri Kosina */ =20 +#include +#include #include +#include #include #include #include +#include #include +#include #include #include +#include +#include +#include + +#define SPI_HID_OUTPUT_REPORT_CONTENT_ID_DESC_REQUEST 0x00 + +#define SPI_HID_RESP_TIMEOUT 1000 + +/* Protocol message size constants */ +#define SPI_HID_OUTPUT_HEADER_LEN 8 + +/* flags */ +/* + * ready flag indicates that the FW is ready to accept commands and + * requests. The FW becomes ready after sending the report descriptor. + */ +#define SPI_HID_READY 0 + +/* Raw input buffer with data from the bus */ +struct spi_hid_input_buf { + u8 header[HIDSPI_INPUT_HEADER_SIZE]; + u8 body[HIDSPI_INPUT_BODY_HEADER_SIZE]; + 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[]; +}; + +/* Data necessary to send an output report */ +struct spi_hid_output_report { + u8 report_type; + u16 content_length; + u8 content_id; + u8 *content; +}; + +/* 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; +}; =20 /* struct spi_hid_conf - Conf provided to the core */ struct spi_hid_conf { @@ -61,8 +117,26 @@ struct spi_hid { struct spihid_ops *ops; struct spi_hid_conf *conf; =20 + struct spi_hid_device_descriptor desc; /* HID device descriptor. */ + struct spi_hid_output_buf *output; /* Output buffer. */ + struct spi_hid_input_buf *input; /* Input buffer. */ + struct spi_hid_input_buf *response; /* Response buffer. */ + + u16 response_length; + u16 bufsize; + enum hidspi_power_state power_state; =20 + u8 reset_attempts; /* The number of reset attempts. */ + + unsigned long flags; /* device flags. */ + + /* Control lock to make sure one output transaction at a time. */ + struct mutex output_lock; + struct completion output_done; + + u32 report_descriptor_crc32; /* HID report descriptor crc32 checksum. */ + u32 regulator_error_count; int regulator_last_error; u32 bus_error_count; @@ -70,6 +144,33 @@ struct spi_hid { u32 dir_count; /* device initiated reset count. */ }; =20 +static struct hid_ll_driver spi_hid_ll_driver; + +static void spi_hid_populate_output_header(u8 *buf, + const struct spi_hid_conf *conf, + const struct spi_hid_output_report *report) +{ + buf[0] =3D conf->write_opcode; + put_unaligned_be24(conf->output_report_address, &buf[1]); + buf[4] =3D report->report_type; + put_unaligned_le16(report->content_length, &buf[5]); + buf[7] =3D report->content_id; +} + +static int spi_hid_output(struct spi_hid *shid, const void *buf, u16 lengt= h) +{ + int error; + + error =3D spi_write(shid->spi, buf, length); + + if (error) { + shid->bus_error_count++; + shid->bus_last_error =3D error; + } + + return error; +} + static const char *spi_hid_power_mode_string(enum hidspi_power_state power= _state) { switch (power_state) { @@ -84,11 +185,416 @@ static const char *spi_hid_power_mode_string(enum hid= spi_power_state power_state } } =20 +static void spi_hid_stop_hid(struct spi_hid *shid) +{ + struct hid_device *hid =3D shid->hid; + + shid->hid =3D NULL; + clear_bit(SPI_HID_READY, &shid->flags); + + if (hid) + hid_destroy_device(hid); +} + +static int spi_hid_send_output_report(struct spi_hid *shid, + struct spi_hid_output_report *report) +{ + struct spi_hid_output_buf *buf =3D shid->output; + struct device *dev =3D &shid->spi->dev; + u16 report_length; + u16 padded_length; + u8 padding; + int error; + + guard(mutex)(&shid->output_lock); + if (report->content_length > shid->desc.max_output_length) { + dev_err(dev, "Output report too big, content_length 0x%x.", + report->content_length); + return -E2BIG; + } + + spi_hid_populate_output_header(buf->header, shid->conf, report); + + if (report->content_length) + memcpy(&buf->content, report->content, report->content_length); + + report_length =3D sizeof(buf->header) + report->content_length; + padded_length =3D round_up(report_length, 4); + padding =3D padded_length - report_length; + memset(&buf->content[report->content_length], 0, padding); + + error =3D spi_hid_output(shid, buf, padded_length); + if (error) + dev_err(dev, "Failed output transfer: %d.", error); + + return error; +} + +static int spi_hid_sync_request(struct spi_hid *shid, + struct spi_hid_output_report *report) +{ + struct device *dev =3D &shid->spi->dev; + int error; + + error =3D spi_hid_send_output_report(shid, report); + if (error) + return error; + + error =3D wait_for_completion_interruptible_timeout(&shid->output_done, + msecs_to_jiffies(SPI_HID_RESP_TIMEOUT)); + if (error =3D=3D 0) { + dev_err(dev, "Response timed out."); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * 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) +{ + struct device *dev =3D &shid->spi->dev; + struct spi_hid_output_report report =3D { + .report_type =3D REPORT_DESCRIPTOR, + .content_length =3D 0, + .content_id =3D SPI_HID_OUTPUT_REPORT_CONTENT_ID_DESC_REQUEST, + .content =3D NULL, + }; + int ret; + + ret =3D spi_hid_sync_request(shid, &report); + if (ret) { + dev_err(dev, + "Expected report descriptor not received: %d.", ret); + return ret; + } + + ret =3D shid->response_length; + if (ret !=3D shid->desc.report_descriptor_length) { + ret =3D min_t(unsigned int, ret, shid->desc.report_descriptor_length); + dev_err(dev, "Received report descriptor length doesn't match device des= criptor field, using min of the two: %d.", + ret); + } + + return ret; +} + +static int spi_hid_create_device(struct spi_hid *shid) +{ + struct hid_device *hid; + struct device *dev =3D &shid->spi->dev; + int error; + + hid =3D hid_allocate_device(); + error =3D PTR_ERR_OR_ZERO(hid); + if (error) { + dev_err(dev, "Failed to allocate hid device: %d.", error); + return error; + } + + hid->driver_data =3D shid->spi; + hid->ll_driver =3D &spi_hid_ll_driver; + hid->dev.parent =3D &shid->spi->dev; + hid->bus =3D BUS_SPI; + hid->version =3D shid->desc.hid_version; + hid->vendor =3D shid->desc.vendor_id; + hid->product =3D shid->desc.product_id; + + snprintf(hid->name, sizeof(hid->name), "spi %04X:%04X", + hid->vendor, hid->product); + strscpy(hid->phys, dev_name(&shid->spi->dev), sizeof(hid->phys)); + + shid->hid =3D hid; + + error =3D hid_add_device(hid); + if (error) { + dev_err(dev, "Failed to add hid device: %d.", error); + /* + * We likely got here because report descriptor request timed + * out. Let's disconnect and destroy the hid_device structure. + */ + spi_hid_stop_hid(shid); + return error; + } + + return 0; +} + +static int spi_hid_get_request(struct spi_hid *shid, u8 content_id) +{ + struct device *dev =3D &shid->spi->dev; + struct spi_hid_output_report report =3D { + .report_type =3D GET_FEATURE, + .content_length =3D 0, + .content_id =3D content_id, + .content =3D NULL, + }; + int error; + + error =3D spi_hid_sync_request(shid, &report); + if (error) { + dev_err(dev, + "Expected get request response not received! Error %d.", + error); + return error; + } + + return 0; +} + +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 =3D { + .report_type =3D SET_FEATURE, + .content_length =3D arg_len, + .content_id =3D content_id, + .content =3D arg_buf, + }; + + return spi_hid_sync_request(shid, &report); +} + +/* This is a placeholder. Will be implemented in the next patch. */ static irqreturn_t spi_hid_dev_irq(int irq, void *_shid) { return IRQ_HANDLED; } =20 +static int spi_hid_alloc_buffers(struct spi_hid *shid, size_t report_size) +{ + struct device *dev =3D &shid->spi->dev; + int inbufsize =3D sizeof(shid->input->header) + sizeof(shid->input->body)= + report_size; + int outbufsize =3D sizeof(shid->output->header) + report_size; + + // devm_krealloc with __GFP_ZERO ensures the new memory is initialized + shid->output =3D devm_krealloc(dev, shid->output, outbufsize, GFP_KERNEL = | __GFP_ZERO); + shid->input =3D devm_krealloc(dev, shid->input, inbufsize, GFP_KERNEL | _= _GFP_ZERO); + shid->response =3D devm_krealloc(dev, shid->response, inbufsize, GFP_KERN= EL | __GFP_ZERO); + + if (!shid->output || !shid->input || !shid->response) + return -ENOMEM; + + shid->bufsize =3D report_size; + + return 0; +} + +static int spi_hid_get_report_length(struct hid_report *report) +{ + return ((report->size - 1) >> 3) + 1 + + report->device->report_enum[report->type].numbered + 2; +} + +/* + * Traverse the supplied list of reports and find the longest + */ +static void spi_hid_find_max_report(struct hid_device *hid, u32 type, + u16 *max) +{ + struct hid_report *report; + u16 size; + + /* + * We should not rely on wMaxInputLength, as some devices may set it to + * a wrong length. + */ + list_for_each_entry(report, &hid->report_enum[type].report_list, list) { + size =3D spi_hid_get_report_length(report); + if (*max < size) + *max =3D size; + } +} + +/* hid_ll_driver interface functions */ + +static int spi_hid_ll_start(struct hid_device *hid) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + int error =3D 0; + u16 bufsize =3D 0; + + spi_hid_find_max_report(hid, HID_INPUT_REPORT, &bufsize); + spi_hid_find_max_report(hid, HID_OUTPUT_REPORT, &bufsize); + spi_hid_find_max_report(hid, HID_FEATURE_REPORT, &bufsize); + + if (bufsize < HID_MIN_BUFFER_SIZE) { + dev_err(&spi->dev, + "HID_MIN_BUFFER_SIZE > max_input_length (%d).", + bufsize); + return -EINVAL; + } + + if (bufsize > shid->bufsize) { + guard(disable_irq)(&shid->spi->irq); + + error =3D spi_hid_alloc_buffers(shid, bufsize); + if (error) + return error; + } + + return 0; +} + +static void spi_hid_ll_stop(struct hid_device *hid) +{ + hid->claimed =3D 0; +} + +static int spi_hid_ll_open(struct hid_device *hid) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + + set_bit(SPI_HID_READY, &shid->flags); + return 0; +} + +static void spi_hid_ll_close(struct hid_device *hid) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + + clear_bit(SPI_HID_READY, &shid->flags); + shid->reset_attempts =3D 0; +} + +static int spi_hid_ll_power(struct hid_device *hid, int level) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + int error =3D 0; + + guard(mutex)(&shid->output_lock); + if (!shid->hid) + error =3D -ENODEV; + + return error; +} + +static int spi_hid_ll_parse(struct hid_device *hid) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + struct device *dev =3D &spi->dev; + int error, len; + + len =3D spi_hid_report_descriptor_request(shid); + if (len < 0) { + dev_err(dev, "Report descriptor request failed, %d.", len); + return len; + } + + /* + * 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. + */ + error =3D hid_parse_report(hid, (u8 *)shid->response->content, len); + if (error) { + dev_err(dev, "failed parsing report: %d.", error); + return error; + } + shid->report_descriptor_crc32 =3D crc32_le(0, + (unsigned char const *)shid->response->content, + len); + + return 0; +} + +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 =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + struct device *dev =3D &spi->dev; + int ret; + + switch (reqtype) { + case HID_REQ_SET_REPORT: + if (buf[0] !=3D reportnum) { + dev_err(dev, "report id mismatch."); + return -EINVAL; + } + + ret =3D spi_hid_set_request(shid, &buf[1], len - 1, + reportnum); + if (ret) { + dev_err(dev, "failed to set report."); + return ret; + } + + ret =3D len; + break; + case HID_REQ_GET_REPORT: + ret =3D spi_hid_get_request(shid, reportnum); + if (ret) { + dev_err(dev, "failed to get report."); + return ret; + } + + ret =3D min_t(size_t, len, + (shid->response->body[1] | (shid->response->body[2] << 8)) + 1); + buf[0] =3D shid->response->body[3]; + memcpy(&buf[1], &shid->response->content, ret); + break; + default: + dev_err(dev, "invalid request type."); + return -EIO; + } + + return ret; +} + +static int spi_hid_ll_output_report(struct hid_device *hid, __u8 *buf, + size_t len) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + struct device *dev =3D &spi->dev; + struct spi_hid_output_report report =3D { + .report_type =3D OUTPUT_REPORT, + .content_length =3D len - 1, + .content_id =3D buf[0], + .content =3D &buf[1], + }; + int error; + + if (!test_bit(SPI_HID_READY, &shid->flags)) { + dev_err(dev, "%s called in unready state", __func__); + return -ENODEV; + } + + if (shid->desc.no_output_report_ack) + error =3D spi_hid_send_output_report(shid, &report); + else + error =3D spi_hid_sync_request(shid, &report); + + if (error) { + dev_err(dev, "failed to send output report."); + return error; + } + + return len; +} + +static struct hid_ll_driver spi_hid_ll_driver =3D { + .start =3D spi_hid_ll_start, + .stop =3D spi_hid_ll_stop, + .open =3D spi_hid_ll_open, + .close =3D spi_hid_ll_close, + .power =3D spi_hid_ll_power, + .parse =3D spi_hid_ll_parse, + .output_report =3D spi_hid_ll_output_report, + .raw_request =3D spi_hid_ll_raw_request, +}; + static ssize_t bus_error_count_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -159,6 +665,15 @@ int spi_hid_core_probe(struct spi_device *spi, struct = spihid_ops *ops, =20 spi_set_drvdata(spi, shid); =20 + /* + * we need to allocate the buffer without knowing the maximum + * size of the reports. Let's use SZ_2K, then we do the + * real computation later. + */ + error =3D spi_hid_alloc_buffers(shid, SZ_2K); + if (error) + return error; + /* * At the end of probe we initialize the device: * 0) assert reset, bias the interrupt line @@ -191,6 +706,8 @@ int spi_hid_core_probe(struct spi_device *spi, struct s= pihid_ops *ops, dev_dbg(dev, "%s: d3 -> %s.", __func__, spi_hid_power_mode_string(shid->power_state)); =20 + spi_hid_create_device(shid); + return 0; } EXPORT_SYMBOL_GPL(spi_hid_core_probe); @@ -201,6 +718,8 @@ void spi_hid_core_remove(struct spi_device *spi) struct device *dev =3D &spi->dev; int error; =20 + spi_hid_stop_hid(shid); + shid->ops->assert_reset(shid->ops); error =3D shid->ops->power_down(shid->ops); if (error) --=20 2.53.0.1185.g05d4b7b318-goog