| /* SPDX-License-Identifier: GPL-2.0-only */ | |
| /* Copyright (c) 2024 Intel Corporation */ | |
| #include <linux/bitfield.h> | |
| #include <linux/hid.h> | |
| #include <linux/hid-over-i2c.h> | |
| #include <linux/unaligned.h> | |
| #include "intel-thc-dev.h" | |
| #include "intel-thc-dma.h" | |
| #include "quicki2c-dev.h" | |
| #include "quicki2c-hid.h" | |
| #include "quicki2c-protocol.h" | |
| static ssize_t quicki2c_init_write_buf(struct quicki2c_device *qcdev, u32 cmd, size_t cmd_len, | |
| bool append_data_reg, u8 *data, size_t data_len, | |
| u8 *write_buf, size_t write_buf_len) | |
| { | |
| size_t buf_len, offset = 0; | |
| buf_len = HIDI2C_REG_LEN + cmd_len; | |
| if (append_data_reg) | |
| buf_len += HIDI2C_REG_LEN; | |
| if (data && data_len) | |
| buf_len += data_len + HIDI2C_LENGTH_LEN; | |
| if (buf_len > write_buf_len) | |
| return -EINVAL; | |
| if (cmd_len) { | |
| memcpy(write_buf, &qcdev->dev_desc.cmd_reg, HIDI2C_REG_LEN); | |
| offset += HIDI2C_REG_LEN; | |
| memcpy(write_buf + offset, &cmd, cmd_len); | |
| offset += cmd_len; | |
| if (append_data_reg) { | |
| memcpy(write_buf + offset, &qcdev->dev_desc.data_reg, HIDI2C_REG_LEN); | |
| offset += HIDI2C_REG_LEN; | |
| } | |
| } else { | |
| memcpy(write_buf, &qcdev->dev_desc.output_reg, HIDI2C_REG_LEN); | |
| offset += HIDI2C_REG_LEN; | |
| } | |
| if (data && data_len) { | |
| put_unaligned_le16(data_len + HIDI2C_LENGTH_LEN, write_buf + offset); | |
| offset += HIDI2C_LENGTH_LEN; | |
| memcpy(write_buf + offset, data, data_len); | |
| } | |
| return buf_len; | |
| } | |
| static size_t quicki2c_encode_cmd(struct quicki2c_device *qcdev, u32 *cmd_buf, | |
| u8 opcode, u8 report_type, u8 report_id) | |
| { | |
| size_t cmd_len; | |
| *cmd_buf = FIELD_PREP(HIDI2C_CMD_OPCODE, opcode) | | |
| FIELD_PREP(HIDI2C_CMD_REPORT_TYPE, report_type); | |
| if (report_id < HIDI2C_CMD_MAX_RI) { | |
| *cmd_buf |= FIELD_PREP(HIDI2C_CMD_REPORT_ID, report_id); | |
| cmd_len = HIDI2C_CMD_LEN; | |
| } else { | |
| *cmd_buf |= FIELD_PREP(HIDI2C_CMD_REPORT_ID, HIDI2C_CMD_MAX_RI) | | |
| FIELD_PREP(HIDI2C_CMD_3RD_BYTE, report_id); | |
| cmd_len = HIDI2C_CMD_LEN_OPT; | |
| } | |
| return cmd_len; | |
| } | |
| static int write_cmd_to_txdma(struct quicki2c_device *qcdev, int opcode, | |
| int report_type, int report_id, u8 *buf, size_t buf_len) | |
| { | |
| size_t cmd_len; | |
| ssize_t len; | |
| u32 cmd; | |
| cmd_len = quicki2c_encode_cmd(qcdev, &cmd, opcode, report_type, report_id); | |
| len = quicki2c_init_write_buf(qcdev, cmd, cmd_len, buf ? true : false, buf, | |
| buf_len, qcdev->report_buf, qcdev->report_len); | |
| if (len < 0) | |
| return len; | |
| return thc_dma_write(qcdev->thc_hw, qcdev->report_buf, len); | |
| } | |
| int quicki2c_set_power(struct quicki2c_device *qcdev, enum hidi2c_power_state power_state) | |
| { | |
| return write_cmd_to_txdma(qcdev, HIDI2C_SET_POWER, HIDI2C_RESERVED, power_state, NULL, 0); | |
| } | |
| int quicki2c_get_device_descriptor(struct quicki2c_device *qcdev) | |
| { | |
| u32 read_len = 0; | |
| int ret; | |
| ret = thc_tic_pio_write_and_read(qcdev->thc_hw, qcdev->hid_desc_addr, | |
| HIDI2C_REG_LEN, NULL, HIDI2C_DEV_DESC_LEN, | |
| &read_len, (u32 *)&qcdev->dev_desc); | |
| if (ret || HIDI2C_DEV_DESC_LEN != read_len) { | |
| dev_err_once(qcdev->dev, "Get device descriptor failed, ret %d, read len %u\n", | |
| ret, read_len); | |
| return -EIO; | |
| } | |
| if (le16_to_cpu(qcdev->dev_desc.bcd_ver) != HIDI2C_HID_DESC_BCDVERSION) | |
| return -EOPNOTSUPP; | |
| return 0; | |
| } | |
| int quicki2c_get_report_descriptor(struct quicki2c_device *qcdev) | |
| { | |
| u16 desc_reg = le16_to_cpu(qcdev->dev_desc.report_desc_reg); | |
| size_t read_len = le16_to_cpu(qcdev->dev_desc.report_desc_len); | |
| u32 prd_len = read_len; | |
| return thc_swdma_read(qcdev->thc_hw, (u8 *)&desc_reg, HIDI2C_REG_LEN, | |
| &prd_len, qcdev->report_descriptor, &read_len); | |
| } | |
| int quicki2c_get_report(struct quicki2c_device *qcdev, u8 report_type, | |
| unsigned int reportnum, void *buf, size_t buf_len) | |
| { | |
| struct hidi2c_report_packet *rpt; | |
| size_t cmd_len, read_len = 0; | |
| int rep_type, ret; | |
| ssize_t len; | |
| u32 cmd; | |
| if (report_type == HID_INPUT_REPORT) { | |
| rep_type = HIDI2C_INPUT; | |
| } else if (report_type == HID_FEATURE_REPORT) { | |
| rep_type = HIDI2C_FEATURE; | |
| } else { | |
| dev_err(qcdev->dev, "Unsupported report type for GET REPORT: %d\n", report_type); | |
| return -EINVAL; | |
| } | |
| cmd_len = quicki2c_encode_cmd(qcdev, &cmd, HIDI2C_GET_REPORT, rep_type, reportnum); | |
| len = quicki2c_init_write_buf(qcdev, cmd, cmd_len, true, NULL, 0, | |
| qcdev->report_buf, qcdev->report_len); | |
| if (len < 0) | |
| return len; | |
| rpt = (struct hidi2c_report_packet *)qcdev->input_buf; | |
| ret = thc_swdma_read(qcdev->thc_hw, qcdev->report_buf, len, NULL, rpt, &read_len); | |
| if (ret) { | |
| dev_err_once(qcdev->dev, "Get report failed, ret %d, read len (%zu vs %zu)\n", | |
| ret, read_len, buf_len); | |
| return ret; | |
| } | |
| if (HIDI2C_DATA_LEN(le16_to_cpu(rpt->len)) != buf_len || rpt->data[0] != reportnum) { | |
| dev_err_once(qcdev->dev, "Invalid packet, len (%d vs %zu) report id (%d vs %d)\n", | |
| le16_to_cpu(rpt->len), buf_len, rpt->data[0], reportnum); | |
| return -EINVAL; | |
| } | |
| memcpy(buf, rpt->data, buf_len); | |
| return buf_len; | |
| } | |
| int quicki2c_set_report(struct quicki2c_device *qcdev, u8 report_type, | |
| unsigned int reportnum, void *buf, size_t buf_len) | |
| { | |
| int rep_type; | |
| int ret; | |
| if (report_type == HID_OUTPUT_REPORT) { | |
| rep_type = HIDI2C_OUTPUT; | |
| } else if (report_type == HID_FEATURE_REPORT) { | |
| rep_type = HIDI2C_FEATURE; | |
| } else { | |
| dev_err(qcdev->dev, "Unsupported report type for SET REPORT: %d\n", report_type); | |
| return -EINVAL; | |
| } | |
| ret = write_cmd_to_txdma(qcdev, HIDI2C_SET_REPORT, rep_type, reportnum, buf, buf_len); | |
| if (ret) { | |
| dev_err_once(qcdev->dev, "Set Report failed, ret %d\n", ret); | |
| return ret; | |
| } | |
| return buf_len; | |
| } | |
| int quicki2c_output_report(struct quicki2c_device *qcdev, void *buf, size_t buf_len) | |
| { | |
| ssize_t len; | |
| int ret; | |
| len = quicki2c_init_write_buf(qcdev, 0, 0, false, buf, buf_len, | |
| qcdev->report_buf, qcdev->report_len); | |
| if (len < 0) | |
| return -EINVAL; | |
| ret = thc_dma_write(qcdev->thc_hw, qcdev->report_buf, len); | |
| if (ret) { | |
| dev_err(qcdev->dev, "Output Report failed, ret %d\n", ret); | |
| return ret; | |
| } | |
| return buf_len; | |
| } | |
| #define HIDI2C_RESET_TIMEOUT 5 | |
| int quicki2c_reset(struct quicki2c_device *qcdev) | |
| { | |
| u16 input_reg = le16_to_cpu(qcdev->dev_desc.input_reg); | |
| size_t read_len = HIDI2C_LENGTH_LEN; | |
| u32 prd_len = read_len; | |
| int ret; | |
| qcdev->reset_ack = false; | |
| qcdev->state = QUICKI2C_RESETING; | |
| ret = write_cmd_to_txdma(qcdev, HIDI2C_RESET, HIDI2C_RESERVED, 0, NULL, 0); | |
| if (ret) { | |
| dev_err_once(qcdev->dev, "Send reset command failed, ret %d\n", ret); | |
| return ret; | |
| } | |
| ret = wait_event_interruptible_timeout(qcdev->reset_ack_wq, qcdev->reset_ack, | |
| HIDI2C_RESET_TIMEOUT * HZ); | |
| if (qcdev->reset_ack) | |
| return 0; | |
| /* | |
| * Manually read reset response if it wasn't received, in case reset interrupt | |
| * was missed by touch device or THC hardware. | |
| */ | |
| ret = thc_tic_pio_read(qcdev->thc_hw, input_reg, read_len, &prd_len, | |
| (u32 *)qcdev->input_buf); | |
| if (ret) { | |
| dev_err_once(qcdev->dev, "Read Reset Response failed, ret %d\n", ret); | |
| return ret; | |
| } | |
| /* | |
| * Check response packet length, it's first 16 bits of packet. | |
| * If response packet length is zero, it's reset response, otherwise not. | |
| */ | |
| if (get_unaligned_le16(qcdev->input_buf)) { | |
| dev_err_once(qcdev->dev, | |
| "Wait reset response timed out ret:%d timeout:%ds\n", | |
| ret, HIDI2C_RESET_TIMEOUT); | |
| return -ETIMEDOUT; | |
| } | |
| qcdev->reset_ack = true; | |
| return 0; | |
| } |