mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 06:01:57 +00:00
Merge branch 'for-6.12/goodix-spi' into for-linus
- Add support for a new Goodix HID over SPI driver (Charles Wang) Note: this driver doesn't rely on the spefication of HID over SPI provided by Microsoft, thus needs a separate driver, not a generic bus transport low level driver.
This commit is contained in:
commit
fe9c6249e8
71
Documentation/devicetree/bindings/input/goodix,gt7986u.yaml
Normal file
71
Documentation/devicetree/bindings/input/goodix,gt7986u.yaml
Normal file
@ -0,0 +1,71 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/input/goodix,gt7986u.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: GOODIX GT7986U SPI HID Touchscreen
|
||||
|
||||
maintainers:
|
||||
- Charles Wang <charles.goodix@gmail.com>
|
||||
|
||||
description: Supports the Goodix GT7986U touchscreen.
|
||||
This touch controller reports data packaged according to the HID protocol,
|
||||
but is incompatible with Microsoft's HID-over-SPI protocol.
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/spi/spi-peripheral-props.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- goodix,gt7986u
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
reset-gpios:
|
||||
maxItems: 1
|
||||
|
||||
goodix,hid-report-addr:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description:
|
||||
The register address for retrieving HID report data.
|
||||
This address is related to the device firmware and may
|
||||
change after a firmware update.
|
||||
|
||||
spi-max-frequency: true
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- reset-gpios
|
||||
- goodix,hid-report-addr
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
touchscreen@0 {
|
||||
compatible = "goodix,gt7986u";
|
||||
reg = <0>;
|
||||
interrupt-parent = <&gpio>;
|
||||
interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
|
||||
reset-gpios = <&gpio1 1 GPIO_ACTIVE_LOW>;
|
||||
spi-max-frequency = <10000000>;
|
||||
goodix,hid-report-addr = <0x22c8c>;
|
||||
};
|
||||
};
|
||||
|
||||
...
|
@ -404,6 +404,12 @@ config HID_VIVALDI_COMMON
|
||||
option so that drivers can use common code to parse the HID
|
||||
descriptors for vivaldi function row keymap.
|
||||
|
||||
config HID_GOODIX_SPI
|
||||
tristate "Goodix GT7986U SPI HID touchscreen"
|
||||
depends on SPI_MASTER
|
||||
help
|
||||
Support for Goodix GT7986U SPI HID touchscreen device.
|
||||
|
||||
config HID_GOOGLE_HAMMER
|
||||
tristate "Google Hammer Keyboard"
|
||||
select HID_VIVALDI_COMMON
|
||||
|
@ -54,6 +54,7 @@ obj-$(CONFIG_HID_GEMBIRD) += hid-gembird.o
|
||||
obj-$(CONFIG_HID_GFRM) += hid-gfrm.o
|
||||
obj-$(CONFIG_HID_GLORIOUS) += hid-glorious.o
|
||||
obj-$(CONFIG_HID_VIVALDI_COMMON) += hid-vivaldi-common.o
|
||||
obj-$(CONFIG_HID_GOODIX_SPI) += hid-goodix-spi.o
|
||||
obj-$(CONFIG_HID_GOOGLE_HAMMER) += hid-google-hammer.o
|
||||
obj-$(CONFIG_HID_GOOGLE_STADIA_FF) += hid-google-stadiaff.o
|
||||
obj-$(CONFIG_HID_VIVALDI) += hid-vivaldi.o
|
||||
|
818
drivers/hid/hid-goodix-spi.c
Normal file
818
drivers/hid/hid-goodix-spi.c
Normal file
@ -0,0 +1,818 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Goodix GT7986U SPI Driver Code for HID.
|
||||
*
|
||||
* Copyright (C) 2024 Godix, Inc.
|
||||
*/
|
||||
#include <asm/unaligned.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#define GOODIX_DEV_CONFIRM_ADDR 0x10000
|
||||
#define GOODIX_HID_DESC_ADDR 0x1058C
|
||||
#define GOODIX_HID_REPORT_DESC_ADDR 0x105AA
|
||||
#define GOODIX_HID_SIGN_ADDR 0x10D32
|
||||
|
||||
#define GOODIX_HID_GET_REPORT_CMD 0x02
|
||||
#define GOODIX_HID_SET_REPORT_CMD 0x03
|
||||
|
||||
#define GOODIX_HID_MAX_INBUF_SIZE 128
|
||||
#define GOODIX_HID_ACK_READY_FLAG 0x01
|
||||
#define GOODIX_HID_REPORT_READY_FLAG 0x80
|
||||
|
||||
#define GOODIX_DEV_CONFIRM_VAL 0xAA
|
||||
|
||||
#define GOODIX_SPI_WRITE_FLAG 0xF0
|
||||
#define GOODIX_SPI_READ_FLAG 0xF1
|
||||
#define GOODIX_SPI_TRANS_PREFIX_LEN 1
|
||||
#define GOODIX_REGISTER_WIDTH 4
|
||||
#define GOODIX_SPI_READ_DUMMY_LEN 3
|
||||
#define GOODIX_SPI_READ_PREFIX_LEN (GOODIX_SPI_TRANS_PREFIX_LEN + \
|
||||
GOODIX_REGISTER_WIDTH + \
|
||||
GOODIX_SPI_READ_DUMMY_LEN)
|
||||
#define GOODIX_SPI_WRITE_PREFIX_LEN (GOODIX_SPI_TRANS_PREFIX_LEN + \
|
||||
GOODIX_REGISTER_WIDTH)
|
||||
|
||||
#define GOODIX_CHECKSUM_SIZE sizeof(u16)
|
||||
#define GOODIX_NORMAL_RESET_DELAY_MS 150
|
||||
|
||||
struct goodix_hid_report_header {
|
||||
u8 flag;
|
||||
__le16 size;
|
||||
} __packed;
|
||||
#define GOODIX_HID_ACK_HEADER_SIZE sizeof(struct goodix_hid_report_header)
|
||||
|
||||
struct goodix_hid_report_package {
|
||||
__le16 size;
|
||||
u8 data[];
|
||||
};
|
||||
|
||||
#define GOODIX_HID_PKG_LEN_SIZE sizeof(u16)
|
||||
#define GOODIX_HID_COOR_DATA_LEN 82
|
||||
#define GOODIX_HID_COOR_PKG_LEN (GOODIX_HID_PKG_LEN_SIZE + \
|
||||
GOODIX_HID_COOR_DATA_LEN)
|
||||
|
||||
/* power state */
|
||||
#define GOODIX_SPI_POWER_ON 0x00
|
||||
#define GOODIX_SPI_POWER_SLEEP 0x01
|
||||
|
||||
/* flags used to record the current device operating state */
|
||||
#define GOODIX_HID_STARTED 0
|
||||
|
||||
struct goodix_hid_report_event {
|
||||
struct goodix_hid_report_header hdr;
|
||||
u8 data[GOODIX_HID_COOR_PKG_LEN];
|
||||
} __packed;
|
||||
|
||||
struct goodix_hid_desc {
|
||||
__le16 desc_length;
|
||||
__le16 bcd_version;
|
||||
__le16 report_desc_length;
|
||||
__le16 report_desc_register;
|
||||
__le16 input_register;
|
||||
__le16 max_input_length;
|
||||
__le16 output_register;
|
||||
__le16 max_output_length;
|
||||
__le16 cmd_register;
|
||||
__le16 data_register;
|
||||
__le16 vendor_id;
|
||||
__le16 product_id;
|
||||
__le16 version_id;
|
||||
__le32 reserved;
|
||||
} __packed;
|
||||
|
||||
struct goodix_ts_data {
|
||||
struct device *dev;
|
||||
struct spi_device *spi;
|
||||
struct hid_device *hid;
|
||||
struct goodix_hid_desc hid_desc;
|
||||
|
||||
struct gpio_desc *reset_gpio;
|
||||
u32 hid_report_addr;
|
||||
|
||||
unsigned long flags;
|
||||
/* lock for hid raw request operation */
|
||||
struct mutex hid_request_lock;
|
||||
/* buffer used to store hid report event */
|
||||
u8 *event_buf;
|
||||
u32 hid_max_event_sz;
|
||||
/* buffer used to do spi data transfer */
|
||||
u8 xfer_buf[SZ_2K] ____cacheline_aligned;
|
||||
};
|
||||
|
||||
static void *goodix_get_event_report(struct goodix_ts_data *ts, u32 addr,
|
||||
u8 *data, size_t len)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(&ts->spi->dev);
|
||||
struct spi_transfer xfers;
|
||||
struct spi_message spi_msg;
|
||||
int error;
|
||||
|
||||
/* buffer format: 0xF1 + addr(4bytes) + dummy(3bytes) + data */
|
||||
data[0] = GOODIX_SPI_READ_FLAG;
|
||||
put_unaligned_be32(addr, data + GOODIX_SPI_TRANS_PREFIX_LEN);
|
||||
|
||||
spi_message_init(&spi_msg);
|
||||
memset(&xfers, 0, sizeof(xfers));
|
||||
xfers.tx_buf = data;
|
||||
xfers.rx_buf = data;
|
||||
xfers.len = GOODIX_SPI_READ_PREFIX_LEN + len;
|
||||
spi_message_add_tail(&xfers, &spi_msg);
|
||||
|
||||
error = spi_sync(spi, &spi_msg);
|
||||
if (error) {
|
||||
dev_err(ts->dev, "spi transfer error: %d", error);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return data + GOODIX_SPI_READ_PREFIX_LEN;
|
||||
}
|
||||
|
||||
static int goodix_spi_read(struct goodix_ts_data *ts, u32 addr,
|
||||
void *data, size_t len)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(&ts->spi->dev);
|
||||
struct spi_transfer xfers;
|
||||
struct spi_message spi_msg;
|
||||
int error;
|
||||
|
||||
if (GOODIX_SPI_READ_PREFIX_LEN + len > sizeof(ts->xfer_buf)) {
|
||||
dev_err(ts->dev, "read data len exceed limit %zu",
|
||||
sizeof(ts->xfer_buf) - GOODIX_SPI_READ_PREFIX_LEN);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* buffer format: 0xF1 + addr(4bytes) + dummy(3bytes) + data */
|
||||
ts->xfer_buf[0] = GOODIX_SPI_READ_FLAG;
|
||||
put_unaligned_be32(addr, ts->xfer_buf + GOODIX_SPI_TRANS_PREFIX_LEN);
|
||||
|
||||
spi_message_init(&spi_msg);
|
||||
memset(&xfers, 0, sizeof(xfers));
|
||||
xfers.tx_buf = ts->xfer_buf;
|
||||
xfers.rx_buf = ts->xfer_buf;
|
||||
xfers.len = GOODIX_SPI_READ_PREFIX_LEN + len;
|
||||
spi_message_add_tail(&xfers, &spi_msg);
|
||||
|
||||
error = spi_sync(spi, &spi_msg);
|
||||
if (error)
|
||||
dev_err(ts->dev, "spi transfer error: %d", error);
|
||||
else
|
||||
memcpy(data, ts->xfer_buf + GOODIX_SPI_READ_PREFIX_LEN, len);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int goodix_spi_write(struct goodix_ts_data *ts, u32 addr,
|
||||
const void *data, size_t len)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(&ts->spi->dev);
|
||||
struct spi_transfer xfers;
|
||||
struct spi_message spi_msg;
|
||||
int error;
|
||||
|
||||
if (GOODIX_SPI_WRITE_PREFIX_LEN + len > sizeof(ts->xfer_buf)) {
|
||||
dev_err(ts->dev, "write data len exceed limit %zu",
|
||||
sizeof(ts->xfer_buf) - GOODIX_SPI_WRITE_PREFIX_LEN);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* buffer format: 0xF0 + addr(4bytes) + data */
|
||||
ts->xfer_buf[0] = GOODIX_SPI_WRITE_FLAG;
|
||||
put_unaligned_be32(addr, ts->xfer_buf + GOODIX_SPI_TRANS_PREFIX_LEN);
|
||||
memcpy(ts->xfer_buf + GOODIX_SPI_WRITE_PREFIX_LEN, data, len);
|
||||
|
||||
spi_message_init(&spi_msg);
|
||||
memset(&xfers, 0, sizeof(xfers));
|
||||
xfers.tx_buf = ts->xfer_buf;
|
||||
xfers.len = GOODIX_SPI_WRITE_PREFIX_LEN + len;
|
||||
spi_message_add_tail(&xfers, &spi_msg);
|
||||
|
||||
error = spi_sync(spi, &spi_msg);
|
||||
if (error)
|
||||
dev_err(ts->dev, "spi transfer error: %d", error);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int goodix_dev_confirm(struct goodix_ts_data *ts)
|
||||
{
|
||||
u8 tx_buf[8], rx_buf[8];
|
||||
int retry = 3;
|
||||
int error;
|
||||
|
||||
gpiod_set_value_cansleep(ts->reset_gpio, 0);
|
||||
usleep_range(4000, 4100);
|
||||
|
||||
memset(tx_buf, GOODIX_DEV_CONFIRM_VAL, sizeof(tx_buf));
|
||||
while (retry--) {
|
||||
error = goodix_spi_write(ts, GOODIX_DEV_CONFIRM_ADDR,
|
||||
tx_buf, sizeof(tx_buf));
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = goodix_spi_read(ts, GOODIX_DEV_CONFIRM_ADDR,
|
||||
rx_buf, sizeof(rx_buf));
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
if (!memcmp(tx_buf, rx_buf, sizeof(tx_buf)))
|
||||
return 0;
|
||||
|
||||
usleep_range(5000, 5100);
|
||||
}
|
||||
|
||||
dev_err(ts->dev, "device confirm failed, rx_buf: %*ph", 8, rx_buf);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* goodix_hid_parse() - hid-core .parse() callback
|
||||
* @hid: hid device instance
|
||||
*
|
||||
* This function gets called during call to hid_add_device
|
||||
*
|
||||
* Return: 0 on success and non zero on error
|
||||
*/
|
||||
static int goodix_hid_parse(struct hid_device *hid)
|
||||
{
|
||||
struct goodix_ts_data *ts = hid->driver_data;
|
||||
u16 rsize;
|
||||
int error;
|
||||
|
||||
rsize = le16_to_cpu(ts->hid_desc.report_desc_length);
|
||||
if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
|
||||
dev_err(ts->dev, "invalid report desc size, %d", rsize);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
u8 *rdesc __free(kfree) = kzalloc(rsize, GFP_KERNEL);
|
||||
if (!rdesc)
|
||||
return -ENOMEM;
|
||||
|
||||
error = goodix_spi_read(ts, GOODIX_HID_REPORT_DESC_ADDR, rdesc, rsize);
|
||||
if (error) {
|
||||
dev_err(ts->dev, "failed get report desc, %d", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
error = hid_parse_report(hid, rdesc, rsize);
|
||||
if (error) {
|
||||
dev_err(ts->dev, "failed parse report, %d", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int goodix_hid_get_report_length(struct hid_report *report)
|
||||
{
|
||||
return ((report->size - 1) >> 3) + 1 +
|
||||
report->device->report_enum[report->type].numbered + 2;
|
||||
}
|
||||
|
||||
static void goodix_hid_find_max_report(struct hid_device *hid, unsigned int type,
|
||||
unsigned int *max)
|
||||
{
|
||||
struct hid_report *report;
|
||||
unsigned int size;
|
||||
|
||||
list_for_each_entry(report, &hid->report_enum[type].report_list, list) {
|
||||
size = goodix_hid_get_report_length(report);
|
||||
if (*max < size)
|
||||
*max = size;
|
||||
}
|
||||
}
|
||||
|
||||
static int goodix_hid_start(struct hid_device *hid)
|
||||
{
|
||||
struct goodix_ts_data *ts = hid->driver_data;
|
||||
unsigned int bufsize = GOODIX_HID_COOR_PKG_LEN;
|
||||
u32 report_size;
|
||||
|
||||
goodix_hid_find_max_report(hid, HID_INPUT_REPORT, &bufsize);
|
||||
goodix_hid_find_max_report(hid, HID_OUTPUT_REPORT, &bufsize);
|
||||
goodix_hid_find_max_report(hid, HID_FEATURE_REPORT, &bufsize);
|
||||
|
||||
report_size = GOODIX_SPI_READ_PREFIX_LEN +
|
||||
GOODIX_HID_ACK_HEADER_SIZE + bufsize;
|
||||
if (report_size <= ts->hid_max_event_sz)
|
||||
return 0;
|
||||
|
||||
ts->event_buf = devm_krealloc(ts->dev, ts->event_buf,
|
||||
report_size, GFP_KERNEL);
|
||||
if (!ts->event_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ts->hid_max_event_sz = report_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void goodix_hid_stop(struct hid_device *hid)
|
||||
{
|
||||
hid->claimed = 0;
|
||||
}
|
||||
|
||||
static int goodix_hid_open(struct hid_device *hid)
|
||||
{
|
||||
struct goodix_ts_data *ts = hid->driver_data;
|
||||
|
||||
set_bit(GOODIX_HID_STARTED, &ts->flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void goodix_hid_close(struct hid_device *hid)
|
||||
{
|
||||
struct goodix_ts_data *ts = hid->driver_data;
|
||||
|
||||
clear_bit(GOODIX_HID_STARTED, &ts->flags);
|
||||
}
|
||||
|
||||
/* Return date length of response data */
|
||||
static int goodix_hid_check_ack_status(struct goodix_ts_data *ts, u32 *resp_len)
|
||||
{
|
||||
struct goodix_hid_report_header hdr;
|
||||
int retry = 20;
|
||||
int error;
|
||||
int len;
|
||||
|
||||
while (retry--) {
|
||||
/*
|
||||
* 3 bytes of hid request response data
|
||||
* - byte 0: Ack flag, value of 1 for data ready
|
||||
* - bytes 1-2: Response data length
|
||||
*/
|
||||
error = goodix_spi_read(ts, ts->hid_report_addr,
|
||||
&hdr, sizeof(hdr));
|
||||
if (!error && (hdr.flag & GOODIX_HID_ACK_READY_FLAG)) {
|
||||
len = le16_to_cpu(hdr.size);
|
||||
if (len < GOODIX_HID_PKG_LEN_SIZE) {
|
||||
dev_err(ts->dev, "hrd.size too short: %d", len);
|
||||
return -EINVAL;
|
||||
}
|
||||
*resp_len = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Wait 10ms for another try */
|
||||
usleep_range(10000, 11000);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* goodix_hid_get_raw_report() - Process hidraw GET REPORT operation
|
||||
* @hid: hid device instance
|
||||
* @reportnum: Report ID
|
||||
* @buf: Buffer for store the report date
|
||||
* @len: Length fo report data
|
||||
* @report_type: Report type
|
||||
*
|
||||
* The function for hid_ll_driver.get_raw_report to handle the HIDRAW ioctl
|
||||
* get report request. The transmitted data follows the standard i2c-hid
|
||||
* protocol with a specified header.
|
||||
*
|
||||
* Return: The length of the data in the buf on success, negative error code
|
||||
*/
|
||||
static int goodix_hid_get_raw_report(struct hid_device *hid,
|
||||
unsigned char reportnum,
|
||||
u8 *buf, size_t len,
|
||||
unsigned char report_type)
|
||||
{
|
||||
struct goodix_ts_data *ts = hid->driver_data;
|
||||
u16 data_register = le16_to_cpu(ts->hid_desc.data_register);
|
||||
u16 cmd_register = le16_to_cpu(ts->hid_desc.cmd_register);
|
||||
u8 tmp_buf[GOODIX_HID_MAX_INBUF_SIZE];
|
||||
int tx_len = 0, args_len = 0;
|
||||
u32 response_data_len;
|
||||
u8 args[3];
|
||||
int error;
|
||||
|
||||
if (report_type == HID_OUTPUT_REPORT)
|
||||
return -EINVAL;
|
||||
|
||||
if (reportnum == 3) {
|
||||
/* Get win8 signature data */
|
||||
error = goodix_spi_read(ts, GOODIX_HID_SIGN_ADDR, buf, len);
|
||||
if (error) {
|
||||
dev_err(ts->dev, "failed get win8 sign: %d", error);
|
||||
return -EINVAL;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
if (reportnum >= 0x0F)
|
||||
args[args_len++] = reportnum;
|
||||
|
||||
put_unaligned_le16(data_register, args + args_len);
|
||||
args_len += sizeof(data_register);
|
||||
|
||||
/* Clean 3 bytes of hid ack header data */
|
||||
memset(tmp_buf, 0, GOODIX_HID_ACK_HEADER_SIZE);
|
||||
tx_len += GOODIX_HID_ACK_HEADER_SIZE;
|
||||
|
||||
put_unaligned_le16(cmd_register, tmp_buf + tx_len);
|
||||
tx_len += sizeof(cmd_register);
|
||||
|
||||
tmp_buf[tx_len] = (report_type == HID_FEATURE_REPORT ? 0x03 : 0x01) << 4;
|
||||
tmp_buf[tx_len] |= reportnum >= 0x0F ? 0x0F : reportnum;
|
||||
tx_len++;
|
||||
|
||||
tmp_buf[tx_len++] = GOODIX_HID_GET_REPORT_CMD;
|
||||
|
||||
memcpy(tmp_buf + tx_len, args, args_len);
|
||||
tx_len += args_len;
|
||||
|
||||
/* Step1: write report request info */
|
||||
error = goodix_spi_write(ts, ts->hid_report_addr, tmp_buf, tx_len);
|
||||
if (error) {
|
||||
dev_err(ts->dev, "failed send read feature cmd, %d", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* No need read response data */
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
/* Step2: check response data status */
|
||||
error = goodix_hid_check_ack_status(ts, &response_data_len);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
len = min(len, response_data_len - GOODIX_HID_PKG_LEN_SIZE);
|
||||
/* Step3: read response data(skip 2bytes of hid pkg length) */
|
||||
error = goodix_spi_read(ts, ts->hid_report_addr +
|
||||
GOODIX_HID_ACK_HEADER_SIZE +
|
||||
GOODIX_HID_PKG_LEN_SIZE, buf, len);
|
||||
if (error) {
|
||||
dev_err(ts->dev, "failed read hid response data, %d", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
if (buf[0] != reportnum) {
|
||||
dev_err(ts->dev, "incorrect report (%d vs %d expected)",
|
||||
buf[0], reportnum);
|
||||
return -EINVAL;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* goodix_hid_set_raw_report() - process hidraw SET REPORT operation
|
||||
* @hid: HID device
|
||||
* @reportnum: Report ID
|
||||
* @buf: Buffer for communication
|
||||
* @len: Length of data in the buffer
|
||||
* @report_type: Report type
|
||||
*
|
||||
* The function for hid_ll_driver.get_raw_report to handle the HIDRAW ioctl
|
||||
* set report request. The transmitted data follows the standard i2c-hid
|
||||
* protocol with a specified header.
|
||||
*
|
||||
* Return: The length of the data sent, negative error code on failure
|
||||
*/
|
||||
static int goodix_hid_set_raw_report(struct hid_device *hid,
|
||||
unsigned char reportnum,
|
||||
__u8 *buf, size_t len,
|
||||
unsigned char report_type)
|
||||
{
|
||||
struct goodix_ts_data *ts = hid->driver_data;
|
||||
u16 data_register = le16_to_cpu(ts->hid_desc.data_register);
|
||||
u16 cmd_register = le16_to_cpu(ts->hid_desc.cmd_register);
|
||||
int tx_len = 0, args_len = 0;
|
||||
u8 tmp_buf[GOODIX_HID_MAX_INBUF_SIZE];
|
||||
u8 args[5];
|
||||
int error;
|
||||
|
||||
if (reportnum >= 0x0F) {
|
||||
args[args_len++] = reportnum;
|
||||
reportnum = 0x0F;
|
||||
}
|
||||
|
||||
put_unaligned_le16(data_register, args + args_len);
|
||||
args_len += sizeof(data_register);
|
||||
|
||||
put_unaligned_le16(GOODIX_HID_PKG_LEN_SIZE + len, args + args_len);
|
||||
args_len += GOODIX_HID_PKG_LEN_SIZE;
|
||||
|
||||
/* Clean 3 bytes of hid ack header data */
|
||||
memset(tmp_buf, 0, GOODIX_HID_ACK_HEADER_SIZE);
|
||||
tx_len += GOODIX_HID_ACK_HEADER_SIZE;
|
||||
|
||||
put_unaligned_le16(cmd_register, tmp_buf + tx_len);
|
||||
tx_len += sizeof(cmd_register);
|
||||
|
||||
tmp_buf[tx_len++] = ((report_type == HID_FEATURE_REPORT ? 0x03 : 0x02) << 4) | reportnum;
|
||||
tmp_buf[tx_len++] = GOODIX_HID_SET_REPORT_CMD;
|
||||
|
||||
memcpy(tmp_buf + tx_len, args, args_len);
|
||||
tx_len += args_len;
|
||||
|
||||
memcpy(tmp_buf + tx_len, buf, len);
|
||||
tx_len += len;
|
||||
|
||||
error = goodix_spi_write(ts, ts->hid_report_addr, tmp_buf, tx_len);
|
||||
if (error) {
|
||||
dev_err(ts->dev, "failed send report: %*ph", tx_len, tmp_buf);
|
||||
return error;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int goodix_hid_raw_request(struct hid_device *hid,
|
||||
unsigned char reportnum,
|
||||
__u8 *buf, size_t len,
|
||||
unsigned char rtype, int reqtype)
|
||||
{
|
||||
struct goodix_ts_data *ts = hid->driver_data;
|
||||
int error = -EINVAL;
|
||||
|
||||
guard(mutex)(&ts->hid_request_lock);
|
||||
switch (reqtype) {
|
||||
case HID_REQ_GET_REPORT:
|
||||
error = goodix_hid_get_raw_report(hid, reportnum, buf,
|
||||
len, rtype);
|
||||
break;
|
||||
case HID_REQ_SET_REPORT:
|
||||
if (buf[0] == reportnum)
|
||||
error = goodix_hid_set_raw_report(hid, reportnum,
|
||||
buf, len, rtype);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static struct hid_ll_driver goodix_hid_ll_driver = {
|
||||
.parse = goodix_hid_parse,
|
||||
.start = goodix_hid_start,
|
||||
.stop = goodix_hid_stop,
|
||||
.open = goodix_hid_open,
|
||||
.close = goodix_hid_close,
|
||||
.raw_request = goodix_hid_raw_request
|
||||
};
|
||||
|
||||
static irqreturn_t goodix_hid_irq(int irq, void *data)
|
||||
{
|
||||
struct goodix_ts_data *ts = data;
|
||||
struct goodix_hid_report_event *event;
|
||||
struct goodix_hid_report_package *pkg;
|
||||
u16 report_size;
|
||||
|
||||
if (!test_bit(GOODIX_HID_STARTED, &ts->flags))
|
||||
return IRQ_HANDLED;
|
||||
/*
|
||||
* First, read buffer with space for header and coordinate package:
|
||||
* - event header = 3 bytes
|
||||
* - coordinate event = GOODIX_HID_COOR_PKG_LEN bytes
|
||||
*
|
||||
* If the data size info in the event header exceeds
|
||||
* GOODIX_HID_COOR_PKG_LEN, it means that there are other packages
|
||||
* besides the coordinate package.
|
||||
*/
|
||||
event = goodix_get_event_report(ts, ts->hid_report_addr, ts->event_buf,
|
||||
GOODIX_HID_ACK_HEADER_SIZE +
|
||||
GOODIX_HID_COOR_PKG_LEN);
|
||||
if (!event) {
|
||||
dev_err(ts->dev, "failed get coordinate data");
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/* Check coordinate data valid falg */
|
||||
if (event->hdr.flag != GOODIX_HID_REPORT_READY_FLAG)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
pkg = (struct goodix_hid_report_package *)event->data;
|
||||
if (le16_to_cpu(pkg->size) < GOODIX_HID_PKG_LEN_SIZE) {
|
||||
dev_err(ts->dev, "invalid coordinate event package size, %d",
|
||||
le16_to_cpu(pkg->size));
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
hid_input_report(ts->hid, HID_INPUT_REPORT, pkg->data,
|
||||
le16_to_cpu(pkg->size) - GOODIX_HID_PKG_LEN_SIZE, 1);
|
||||
|
||||
report_size = le16_to_cpu(event->hdr.size);
|
||||
/* Check if there are other packages */
|
||||
if (report_size <= GOODIX_HID_COOR_PKG_LEN)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
if (report_size >= ts->hid_max_event_sz) {
|
||||
dev_err(ts->dev, "package size exceed limit %d vs %d",
|
||||
report_size, ts->hid_max_event_sz);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/* Read the package behind the coordinate data */
|
||||
pkg = goodix_get_event_report(ts, ts->hid_report_addr + sizeof(*event),
|
||||
ts->event_buf,
|
||||
report_size - GOODIX_HID_COOR_PKG_LEN);
|
||||
if (!pkg) {
|
||||
dev_err(ts->dev, "failed read attachment data content");
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
hid_input_report(ts->hid, HID_INPUT_REPORT, pkg->data,
|
||||
le16_to_cpu(pkg->size) - GOODIX_HID_PKG_LEN_SIZE, 1);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int goodix_hid_init(struct goodix_ts_data *ts)
|
||||
{
|
||||
struct hid_device *hid;
|
||||
int error;
|
||||
|
||||
/* Get hid descriptor */
|
||||
error = goodix_spi_read(ts, GOODIX_HID_DESC_ADDR, &ts->hid_desc,
|
||||
sizeof(ts->hid_desc));
|
||||
if (error) {
|
||||
dev_err(ts->dev, "failed get hid desc, %d", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
hid = hid_allocate_device();
|
||||
if (IS_ERR(hid))
|
||||
return PTR_ERR(hid);
|
||||
|
||||
hid->driver_data = ts;
|
||||
hid->ll_driver = &goodix_hid_ll_driver;
|
||||
hid->bus = BUS_SPI;
|
||||
hid->dev.parent = &ts->spi->dev;
|
||||
|
||||
hid->version = le16_to_cpu(ts->hid_desc.bcd_version);
|
||||
hid->vendor = le16_to_cpu(ts->hid_desc.vendor_id);
|
||||
hid->product = le16_to_cpu(ts->hid_desc.product_id);
|
||||
snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "hid-gdix",
|
||||
hid->vendor, hid->product);
|
||||
|
||||
error = hid_add_device(hid);
|
||||
if (error) {
|
||||
dev_err(ts->dev, "failed add hid device, %d", error);
|
||||
hid_destroy_device(hid);
|
||||
return error;
|
||||
}
|
||||
|
||||
ts->hid = hid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int goodix_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct device *dev = &spi->dev;
|
||||
struct goodix_ts_data *ts;
|
||||
int error;
|
||||
|
||||
/* init spi_device */
|
||||
spi->mode = SPI_MODE_0;
|
||||
spi->bits_per_word = 8;
|
||||
error = spi_setup(spi);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
|
||||
if (!ts)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&ts->hid_request_lock);
|
||||
spi_set_drvdata(spi, ts);
|
||||
ts->spi = spi;
|
||||
ts->dev = dev;
|
||||
ts->hid_max_event_sz = GOODIX_SPI_READ_PREFIX_LEN +
|
||||
GOODIX_HID_ACK_HEADER_SIZE + GOODIX_HID_COOR_PKG_LEN;
|
||||
ts->event_buf = devm_kmalloc(dev, ts->hid_max_event_sz, GFP_KERNEL);
|
||||
if (!ts->event_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(ts->reset_gpio))
|
||||
return dev_err_probe(dev, PTR_ERR(ts->reset_gpio),
|
||||
"failed to request reset gpio\n");
|
||||
|
||||
error = device_property_read_u32(dev, "goodix,hid-report-addr",
|
||||
&ts->hid_report_addr);
|
||||
if (error)
|
||||
return dev_err_probe(dev, error,
|
||||
"failed get hid report addr\n");
|
||||
|
||||
error = goodix_dev_confirm(ts);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
/* Waits 150ms for firmware to fully boot */
|
||||
msleep(GOODIX_NORMAL_RESET_DELAY_MS);
|
||||
|
||||
error = goodix_hid_init(ts);
|
||||
if (error) {
|
||||
dev_err(dev, "failed init hid device");
|
||||
return error;
|
||||
}
|
||||
|
||||
error = devm_request_threaded_irq(&ts->spi->dev, ts->spi->irq,
|
||||
NULL, goodix_hid_irq, IRQF_ONESHOT,
|
||||
"goodix_spi_hid", ts);
|
||||
if (error) {
|
||||
dev_err(ts->dev, "could not register interrupt, irq = %d, %d",
|
||||
ts->spi->irq, error);
|
||||
goto err_destroy_hid;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_destroy_hid:
|
||||
hid_destroy_device(ts->hid);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void goodix_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
struct goodix_ts_data *ts = spi_get_drvdata(spi);
|
||||
|
||||
disable_irq(spi->irq);
|
||||
hid_destroy_device(ts->hid);
|
||||
}
|
||||
|
||||
static int goodix_spi_set_power(struct goodix_ts_data *ts, int power_state)
|
||||
{
|
||||
u8 power_control_cmd[] = {0x00, 0x00, 0x00, 0x87, 0x02, 0x00, 0x08};
|
||||
int error;
|
||||
|
||||
/* value 0 for power on, 1 for power sleep */
|
||||
power_control_cmd[5] = power_state;
|
||||
|
||||
guard(mutex)(&ts->hid_request_lock);
|
||||
error = goodix_spi_write(ts, ts->hid_report_addr, power_control_cmd,
|
||||
sizeof(power_control_cmd));
|
||||
if (error) {
|
||||
dev_err(ts->dev, "failed set power mode: %s",
|
||||
power_state == GOODIX_SPI_POWER_ON ? "on" : "sleep");
|
||||
return error;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int goodix_spi_suspend(struct device *dev)
|
||||
{
|
||||
struct goodix_ts_data *ts = dev_get_drvdata(dev);
|
||||
|
||||
disable_irq(ts->spi->irq);
|
||||
return goodix_spi_set_power(ts, GOODIX_SPI_POWER_SLEEP);
|
||||
}
|
||||
|
||||
static int goodix_spi_resume(struct device *dev)
|
||||
{
|
||||
struct goodix_ts_data *ts = dev_get_drvdata(dev);
|
||||
|
||||
enable_irq(ts->spi->irq);
|
||||
return goodix_spi_set_power(ts, GOODIX_SPI_POWER_ON);
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(goodix_spi_pm_ops,
|
||||
goodix_spi_suspend, goodix_spi_resume);
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static const struct acpi_device_id goodix_spi_acpi_match[] = {
|
||||
{ "GXTS7986" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, goodix_spi_acpi_match);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id goodix_spi_of_match[] = {
|
||||
{ .compatible = "goodix,gt7986u", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, goodix_spi_of_match);
|
||||
#endif
|
||||
|
||||
static const struct spi_device_id goodix_spi_ids[] = {
|
||||
{ "gt7986u" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, goodix_spi_ids);
|
||||
|
||||
static struct spi_driver goodix_spi_driver = {
|
||||
.driver = {
|
||||
.name = "goodix-spi-hid",
|
||||
.acpi_match_table = ACPI_PTR(goodix_spi_acpi_match),
|
||||
.of_match_table = of_match_ptr(goodix_spi_of_match),
|
||||
.pm = pm_sleep_ptr(&goodix_spi_pm_ops),
|
||||
},
|
||||
.probe = goodix_spi_probe,
|
||||
.remove = goodix_spi_remove,
|
||||
.id_table = goodix_spi_ids,
|
||||
};
|
||||
module_spi_driver(goodix_spi_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Goodix SPI driver for HID touchscreen");
|
||||
MODULE_AUTHOR("Goodix, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Reference in New Issue
Block a user