| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Goodix GT7986U SPI Driver Code for HID. |
| * |
| * Copyright (C) 2024 Godix, Inc. |
| */ |
| #include <linux/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 |
| |
| 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), |
| .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"); |