| /* |
| * Synaptics TCM touchscreen driver |
| * |
| * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. |
| * |
| * Copyright (C) 2017-2019 Scott Lin <scott.lin@tw.synaptics.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS |
| * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, |
| * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. |
| * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION |
| * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED |
| * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES |
| * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' |
| * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. |
| * DOLLARS. |
| */ |
| |
| #include <linux/input/mt.h> |
| #include <linux/interrupt.h> |
| #include "synaptics_tcm_core.h" |
| |
| #define TYPE_B_PROTOCOL |
| |
| #define USE_DEFAULT_TOUCH_REPORT_CONFIG |
| |
| #define TOUCH_REPORT_CONFIG_SIZE 128 |
| |
| enum touch_status { |
| LIFT = 0, |
| FINGER = 1, |
| GLOVED_FINGER = 2, |
| NOP = -1, |
| }; |
| |
| enum touch_report_code { |
| TOUCH_END = 0, |
| TOUCH_FOREACH_ACTIVE_OBJECT, |
| TOUCH_FOREACH_OBJECT, |
| TOUCH_FOREACH_END, |
| TOUCH_PAD_TO_NEXT_BYTE, |
| TOUCH_TIMESTAMP, |
| TOUCH_OBJECT_N_INDEX, |
| TOUCH_OBJECT_N_CLASSIFICATION, |
| TOUCH_OBJECT_N_X_POSITION, |
| TOUCH_OBJECT_N_Y_POSITION, |
| TOUCH_OBJECT_N_Z, |
| TOUCH_OBJECT_N_X_WIDTH, |
| TOUCH_OBJECT_N_Y_WIDTH, |
| TOUCH_OBJECT_N_TX_POSITION_TIXELS, |
| TOUCH_OBJECT_N_RX_POSITION_TIXELS, |
| TOUCH_0D_BUTTONS_STATE, |
| TOUCH_GESTURE_DOUBLE_TAP, |
| TOUCH_FRAME_RATE, |
| TOUCH_POWER_IM, |
| TOUCH_CID_IM, |
| TOUCH_RAIL_IM, |
| TOUCH_CID_VARIANCE_IM, |
| TOUCH_NSM_FREQUENCY, |
| TOUCH_NSM_STATE, |
| TOUCH_NUM_OF_ACTIVE_OBJECTS, |
| TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME, |
| TOUCH_TUNING_GAUSSIAN_WIDTHS = 0x80, |
| TOUCH_TUNING_SMALL_OBJECT_PARAMS, |
| TOUCH_TUNING_0D_BUTTONS_VARIANCE, |
| }; |
| |
| struct object_data { |
| unsigned char status; |
| unsigned int x_pos; |
| unsigned int y_pos; |
| unsigned int x_width; |
| unsigned int y_width; |
| unsigned int z; |
| unsigned int tx_pos; |
| unsigned int rx_pos; |
| }; |
| |
| struct input_params { |
| unsigned int max_x; |
| unsigned int max_y; |
| unsigned int max_objects; |
| }; |
| |
| struct touch_data { |
| struct object_data *object_data; |
| unsigned int timestamp; |
| unsigned int buttons_state; |
| unsigned int gesture_double_tap; |
| unsigned int frame_rate; |
| unsigned int power_im; |
| unsigned int cid_im; |
| unsigned int rail_im; |
| unsigned int cid_variance_im; |
| unsigned int nsm_frequency; |
| unsigned int nsm_state; |
| unsigned int num_of_active_objects; |
| unsigned int num_of_cpu_cycles; |
| }; |
| |
| struct touch_hcd { |
| bool irq_wake; |
| bool report_touch; |
| bool suspend_touch; |
| unsigned char *prev_status; |
| unsigned int max_x; |
| unsigned int max_y; |
| unsigned int max_objects; |
| struct mutex report_mutex; |
| struct input_dev *input_dev; |
| struct touch_data touch_data; |
| struct input_params input_params; |
| struct syna_tcm_buffer out; |
| struct syna_tcm_buffer resp; |
| struct syna_tcm_hcd *tcm_hcd; |
| }; |
| |
| DECLARE_COMPLETION(touch_remove_complete); |
| |
| static struct touch_hcd *touch_hcd; |
| |
| /** |
| * touch_free_objects() - Free all touch objects |
| * |
| * Report finger lift events to the input subsystem for all touch objects. |
| */ |
| static void touch_free_objects(void) |
| { |
| #ifdef TYPE_B_PROTOCOL |
| unsigned int idx; |
| #endif |
| |
| if (touch_hcd->input_dev == NULL) |
| return; |
| |
| mutex_lock(&touch_hcd->report_mutex); |
| |
| #ifdef TYPE_B_PROTOCOL |
| for (idx = 0; idx < touch_hcd->max_objects; idx++) { |
| input_mt_slot(touch_hcd->input_dev, idx); |
| input_mt_report_slot_state(touch_hcd->input_dev, |
| MT_TOOL_FINGER, 0); |
| } |
| #endif |
| input_report_key(touch_hcd->input_dev, |
| BTN_TOUCH, 0); |
| input_report_key(touch_hcd->input_dev, |
| BTN_TOOL_FINGER, 0); |
| #ifndef TYPE_B_PROTOCOL |
| input_mt_sync(touch_hcd->input_dev); |
| #endif |
| input_sync(touch_hcd->input_dev); |
| |
| mutex_unlock(&touch_hcd->report_mutex); |
| } |
| |
| /** |
| * touch_get_report_data() - Retrieve data from touch report |
| * |
| * Retrieve data from the touch report based on the bit offset and bit length |
| * information from the touch report configuration. |
| */ |
| static int touch_get_report_data(unsigned int offset, |
| unsigned int bits, unsigned int *data) |
| { |
| unsigned char mask; |
| unsigned char byte_data; |
| unsigned int output_data; |
| unsigned int bit_offset; |
| unsigned int byte_offset; |
| unsigned int data_bits; |
| unsigned int available_bits; |
| unsigned int remaining_bits; |
| unsigned char *touch_report; |
| struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; |
| |
| if (bits == 0 || bits > 32) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Invalid number of bits\n"); |
| return -EINVAL; |
| } |
| |
| if (offset + bits > tcm_hcd->report.buffer.data_length * 8) { |
| *data = 0; |
| return 0; |
| } |
| |
| touch_report = tcm_hcd->report.buffer.buf; |
| |
| output_data = 0; |
| remaining_bits = bits; |
| |
| bit_offset = offset % 8; |
| byte_offset = offset / 8; |
| |
| while (remaining_bits) { |
| byte_data = touch_report[byte_offset]; |
| byte_data >>= bit_offset; |
| |
| available_bits = 8 - bit_offset; |
| data_bits = MIN(available_bits, remaining_bits); |
| mask = 0xff >> (8 - data_bits); |
| |
| byte_data &= mask; |
| |
| output_data |= byte_data << (bits - remaining_bits); |
| |
| bit_offset = 0; |
| byte_offset += 1; |
| remaining_bits -= data_bits; |
| } |
| |
| *data = output_data; |
| |
| return 0; |
| } |
| |
| /** |
| * touch_parse_report() - Parse touch report |
| * |
| * Traverse through the touch report configuration and parse the touch report |
| * generated by the device accordingly to retrieve the touch data. |
| */ |
| static int touch_parse_report(void) |
| { |
| int retval; |
| bool active_only; |
| bool num_of_active_objects; |
| unsigned char code; |
| unsigned int size; |
| unsigned int idx; |
| unsigned int obj; |
| unsigned int next; |
| unsigned int data; |
| unsigned int bits; |
| unsigned int offset; |
| unsigned int objects; |
| unsigned int active_objects; |
| unsigned int report_size; |
| unsigned int config_size; |
| unsigned char *config_data; |
| struct touch_data *touch_data; |
| struct object_data *object_data; |
| struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; |
| static unsigned int end_of_foreach; |
| |
| touch_data = &touch_hcd->touch_data; |
| object_data = touch_hcd->touch_data.object_data; |
| |
| config_data = tcm_hcd->config.buf; |
| config_size = tcm_hcd->config.data_length; |
| |
| report_size = tcm_hcd->report.buffer.data_length; |
| |
| size = sizeof(*object_data) * touch_hcd->max_objects; |
| memset(touch_hcd->touch_data.object_data, 0x00, size); |
| |
| num_of_active_objects = false; |
| |
| idx = 0; |
| offset = 0; |
| objects = 0; |
| while (idx < config_size) { |
| code = config_data[idx++]; |
| switch (code) { |
| case TOUCH_END: |
| goto exit; |
| case TOUCH_FOREACH_ACTIVE_OBJECT: |
| obj = 0; |
| next = idx; |
| active_only = true; |
| break; |
| case TOUCH_FOREACH_OBJECT: |
| obj = 0; |
| next = idx; |
| active_only = false; |
| break; |
| case TOUCH_FOREACH_END: |
| end_of_foreach = idx; |
| if (active_only) { |
| if (num_of_active_objects) { |
| objects++; |
| if (objects < active_objects) |
| idx = next; |
| } else if (offset < report_size * 8) { |
| idx = next; |
| } |
| } else { |
| obj++; |
| if (obj < touch_hcd->max_objects) |
| idx = next; |
| } |
| break; |
| case TOUCH_PAD_TO_NEXT_BYTE: |
| offset = ceil_div(offset, 8) * 8; |
| break; |
| case TOUCH_TIMESTAMP: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get timestamp\n"); |
| return retval; |
| } |
| touch_data->timestamp = data; |
| offset += bits; |
| break; |
| case TOUCH_OBJECT_N_INDEX: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &obj); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get object index\n"); |
| return retval; |
| } |
| offset += bits; |
| break; |
| case TOUCH_OBJECT_N_CLASSIFICATION: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get classification data\n"); |
| return retval; |
| } |
| object_data[obj].status = data; |
| offset += bits; |
| break; |
| case TOUCH_OBJECT_N_X_POSITION: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get object x position\n"); |
| return retval; |
| } |
| object_data[obj].x_pos = data; |
| offset += bits; |
| break; |
| case TOUCH_OBJECT_N_Y_POSITION: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get object y position\n"); |
| return retval; |
| } |
| object_data[obj].y_pos = data; |
| offset += bits; |
| break; |
| case TOUCH_OBJECT_N_Z: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get object z\n"); |
| return retval; |
| } |
| object_data[obj].z = data; |
| offset += bits; |
| break; |
| case TOUCH_OBJECT_N_X_WIDTH: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get object x width\n"); |
| return retval; |
| } |
| object_data[obj].x_width = data; |
| offset += bits; |
| break; |
| case TOUCH_OBJECT_N_Y_WIDTH: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get object y width\n"); |
| return retval; |
| } |
| object_data[obj].y_width = data; |
| offset += bits; |
| break; |
| case TOUCH_OBJECT_N_TX_POSITION_TIXELS: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get object tx position\n"); |
| return retval; |
| } |
| object_data[obj].tx_pos = data; |
| offset += bits; |
| break; |
| case TOUCH_OBJECT_N_RX_POSITION_TIXELS: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get object rx position\n"); |
| return retval; |
| } |
| object_data[obj].rx_pos = data; |
| offset += bits; |
| break; |
| case TOUCH_0D_BUTTONS_STATE: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get 0D buttons state\n"); |
| return retval; |
| } |
| touch_data->buttons_state = data; |
| offset += bits; |
| break; |
| case TOUCH_GESTURE_DOUBLE_TAP: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get gesture double tap\n"); |
| return retval; |
| } |
| touch_data->gesture_double_tap = data; |
| offset += bits; |
| break; |
| case TOUCH_FRAME_RATE: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get frame rate\n"); |
| return retval; |
| } |
| touch_data->frame_rate = data; |
| offset += bits; |
| break; |
| case TOUCH_POWER_IM: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get power IM\n"); |
| return retval; |
| } |
| touch_data->power_im = data; |
| offset += bits; |
| break; |
| case TOUCH_CID_IM: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get CID IM\n"); |
| return retval; |
| } |
| touch_data->cid_im = data; |
| offset += bits; |
| break; |
| case TOUCH_RAIL_IM: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get rail IM\n"); |
| return retval; |
| } |
| touch_data->rail_im = data; |
| offset += bits; |
| break; |
| case TOUCH_CID_VARIANCE_IM: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get CID variance IM\n"); |
| return retval; |
| } |
| touch_data->cid_variance_im = data; |
| offset += bits; |
| break; |
| case TOUCH_NSM_FREQUENCY: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get NSM frequency\n"); |
| return retval; |
| } |
| touch_data->nsm_frequency = data; |
| offset += bits; |
| break; |
| case TOUCH_NSM_STATE: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get NSM state\n"); |
| return retval; |
| } |
| touch_data->nsm_state = data; |
| offset += bits; |
| break; |
| case TOUCH_NUM_OF_ACTIVE_OBJECTS: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get number of objects\n"); |
| return retval; |
| } |
| active_objects = data; |
| num_of_active_objects = true; |
| touch_data->num_of_active_objects = data; |
| offset += bits; |
| if (touch_data->num_of_active_objects == 0) |
| idx = end_of_foreach; |
| break; |
| case TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME: |
| bits = config_data[idx++]; |
| retval = touch_get_report_data(offset, bits, &data); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get number of CPU cycles\n"); |
| return retval; |
| } |
| touch_data->num_of_cpu_cycles = data; |
| offset += bits; |
| break; |
| case TOUCH_TUNING_GAUSSIAN_WIDTHS: |
| bits = config_data[idx++]; |
| offset += bits; |
| break; |
| case TOUCH_TUNING_SMALL_OBJECT_PARAMS: |
| bits = config_data[idx++]; |
| offset += bits; |
| break; |
| case TOUCH_TUNING_0D_BUTTONS_VARIANCE: |
| bits = config_data[idx++]; |
| offset += bits; |
| break; |
| } |
| } |
| |
| exit: |
| return 0; |
| } |
| |
| /** |
| * touch_report() - Report touch events |
| * |
| * Retrieve data from the touch report generated by the device and report touch |
| * events to the input subsystem. |
| */ |
| static void touch_report(void) |
| { |
| int retval; |
| unsigned int idx; |
| unsigned int x; |
| unsigned int y; |
| unsigned int temp; |
| unsigned int status; |
| unsigned int touch_count; |
| struct touch_data *touch_data; |
| struct object_data *object_data; |
| struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; |
| const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; |
| |
| if (!touch_hcd->report_touch) |
| return; |
| |
| if (touch_hcd->input_dev == NULL) |
| return; |
| |
| mutex_lock(&touch_hcd->report_mutex); |
| |
| retval = touch_parse_report(); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to parse touch report\n"); |
| goto exit; |
| } |
| |
| touch_data = &touch_hcd->touch_data; |
| object_data = touch_hcd->touch_data.object_data; |
| |
| #ifdef WAKEUP_GESTURE |
| if (touch_data->gesture_double_tap && tcm_hcd->in_suspend) { |
| input_report_key(touch_hcd->input_dev, KEY_WAKEUP, 1); |
| input_sync(touch_hcd->input_dev); |
| input_report_key(touch_hcd->input_dev, KEY_WAKEUP, 0); |
| input_sync(touch_hcd->input_dev); |
| } |
| #endif |
| |
| if (tcm_hcd->in_suspend) |
| goto exit; |
| |
| touch_count = 0; |
| |
| for (idx = 0; idx < touch_hcd->max_objects; idx++) { |
| if (touch_hcd->prev_status[idx] == LIFT && |
| object_data[idx].status == LIFT) |
| status = NOP; |
| else |
| status = object_data[idx].status; |
| |
| switch (status) { |
| case LIFT: |
| #ifdef TYPE_B_PROTOCOL |
| input_mt_slot(touch_hcd->input_dev, idx); |
| input_mt_report_slot_state(touch_hcd->input_dev, |
| MT_TOOL_FINGER, 0); |
| #endif |
| break; |
| case FINGER: |
| case GLOVED_FINGER: |
| x = object_data[idx].x_pos; |
| y = object_data[idx].y_pos; |
| if (bdata->swap_axes) { |
| temp = x; |
| x = y; |
| y = temp; |
| } |
| if (bdata->x_flip) |
| x = touch_hcd->input_params.max_x - x; |
| if (bdata->y_flip) |
| y = touch_hcd->input_params.max_y - y; |
| #ifdef TYPE_B_PROTOCOL |
| input_mt_slot(touch_hcd->input_dev, idx); |
| input_mt_report_slot_state(touch_hcd->input_dev, |
| MT_TOOL_FINGER, 1); |
| #endif |
| input_report_key(touch_hcd->input_dev, |
| BTN_TOUCH, 1); |
| input_report_key(touch_hcd->input_dev, |
| BTN_TOOL_FINGER, 1); |
| input_report_abs(touch_hcd->input_dev, |
| ABS_MT_POSITION_X, x); |
| input_report_abs(touch_hcd->input_dev, |
| ABS_MT_POSITION_Y, y); |
| #ifndef TYPE_B_PROTOCOL |
| input_mt_sync(touch_hcd->input_dev); |
| #endif |
| LOGD(tcm_hcd->pdev->dev.parent, |
| "Finger %d: x = %d, y = %d\n", |
| idx, x, y); |
| touch_count++; |
| break; |
| default: |
| break; |
| } |
| |
| touch_hcd->prev_status[idx] = object_data[idx].status; |
| } |
| |
| if (touch_count == 0) { |
| input_report_key(touch_hcd->input_dev, |
| BTN_TOUCH, 0); |
| input_report_key(touch_hcd->input_dev, |
| BTN_TOOL_FINGER, 0); |
| #ifndef TYPE_B_PROTOCOL |
| input_mt_sync(touch_hcd->input_dev); |
| #endif |
| } |
| |
| input_sync(touch_hcd->input_dev); |
| |
| exit: |
| mutex_unlock(&touch_hcd->report_mutex); |
| } |
| |
| /** |
| * touch_set_input_params() - Set input parameters |
| * |
| * Set the input parameters of the input device based on the information |
| * retrieved from the application information packet. In addition, set up an |
| * array for tracking the status of touch objects. |
| */ |
| static int touch_set_input_params(void) |
| { |
| struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; |
| |
| input_set_abs_params(touch_hcd->input_dev, |
| ABS_MT_POSITION_X, 0, touch_hcd->max_x, 0, 0); |
| input_set_abs_params(touch_hcd->input_dev, |
| ABS_MT_POSITION_Y, 0, touch_hcd->max_y, 0, 0); |
| |
| input_mt_init_slots(touch_hcd->input_dev, touch_hcd->max_objects, |
| INPUT_MT_DIRECT); |
| |
| touch_hcd->input_params.max_x = touch_hcd->max_x; |
| touch_hcd->input_params.max_y = touch_hcd->max_y; |
| touch_hcd->input_params.max_objects = touch_hcd->max_objects; |
| |
| if (touch_hcd->max_objects == 0) |
| return 0; |
| |
| kfree(touch_hcd->prev_status); |
| touch_hcd->prev_status = kzalloc(touch_hcd->max_objects, GFP_KERNEL); |
| if (!touch_hcd->prev_status) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to allocate memory for prev_status\n"); |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * touch_get_input_params() - Get input parameters |
| * |
| * Retrieve the input parameters to register with the input subsystem for |
| * the input device from the application information packet. In addition, |
| * the touch report configuration is retrieved and stored. |
| */ |
| static int touch_get_input_params(void) |
| { |
| int retval; |
| unsigned int temp; |
| struct syna_tcm_app_info *app_info; |
| struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; |
| const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; |
| |
| app_info = &tcm_hcd->app_info; |
| touch_hcd->max_x = le2_to_uint(app_info->max_x); |
| touch_hcd->max_y = le2_to_uint(app_info->max_y); |
| touch_hcd->max_objects = le2_to_uint(app_info->max_objects); |
| |
| if (bdata->swap_axes) { |
| temp = touch_hcd->max_x; |
| touch_hcd->max_x = touch_hcd->max_y; |
| touch_hcd->max_y = temp; |
| } |
| |
| LOCK_BUFFER(tcm_hcd->config); |
| |
| retval = tcm_hcd->write_message(tcm_hcd, |
| CMD_GET_TOUCH_REPORT_CONFIG, |
| NULL, |
| 0, |
| &tcm_hcd->config.buf, |
| &tcm_hcd->config.buf_size, |
| &tcm_hcd->config.data_length, |
| NULL, |
| 0); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to write command %s\n", |
| STR(CMD_GET_TOUCH_REPORT_CONFIG)); |
| UNLOCK_BUFFER(tcm_hcd->config); |
| return retval; |
| } |
| |
| UNLOCK_BUFFER(tcm_hcd->config); |
| |
| return 0; |
| } |
| |
| /** |
| * touch_set_input_dev() - Set up input device |
| * |
| * Allocate an input device, configure the input device based on the particular |
| * input events to be reported, and register the input device with the input |
| * subsystem. |
| */ |
| static int touch_set_input_dev(void) |
| { |
| int retval; |
| struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; |
| |
| touch_hcd->input_dev = input_allocate_device(); |
| if (touch_hcd->input_dev == NULL) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to allocate input device\n"); |
| return -ENODEV; |
| } |
| |
| touch_hcd->input_dev->name = TOUCH_INPUT_NAME; |
| touch_hcd->input_dev->phys = TOUCH_INPUT_PHYS_PATH; |
| touch_hcd->input_dev->id.product = SYNAPTICS_TCM_ID_PRODUCT; |
| touch_hcd->input_dev->id.version = SYNAPTICS_TCM_ID_VERSION; |
| touch_hcd->input_dev->dev.parent = tcm_hcd->pdev->dev.parent; |
| input_set_drvdata(touch_hcd->input_dev, tcm_hcd); |
| |
| set_bit(EV_SYN, touch_hcd->input_dev->evbit); |
| set_bit(EV_KEY, touch_hcd->input_dev->evbit); |
| set_bit(EV_ABS, touch_hcd->input_dev->evbit); |
| set_bit(BTN_TOUCH, touch_hcd->input_dev->keybit); |
| set_bit(BTN_TOOL_FINGER, touch_hcd->input_dev->keybit); |
| #ifdef INPUT_PROP_DIRECT |
| set_bit(INPUT_PROP_DIRECT, touch_hcd->input_dev->propbit); |
| #endif |
| |
| #ifdef WAKEUP_GESTURE |
| set_bit(KEY_WAKEUP, touch_hcd->input_dev->keybit); |
| input_set_capability(touch_hcd->input_dev, EV_KEY, KEY_WAKEUP); |
| #endif |
| |
| retval = touch_set_input_params(); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to set input parameters\n"); |
| input_free_device(touch_hcd->input_dev); |
| touch_hcd->input_dev = NULL; |
| return retval; |
| } |
| |
| retval = input_register_device(touch_hcd->input_dev); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to register input device\n"); |
| input_free_device(touch_hcd->input_dev); |
| touch_hcd->input_dev = NULL; |
| return retval; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * touch_set_report_config() - Set touch report configuration |
| * |
| * Send the SET_TOUCH_REPORT_CONFIG command to configure the format and content |
| * of the touch report. |
| */ |
| static int touch_set_report_config(void) |
| { |
| int retval; |
| unsigned int idx; |
| unsigned int length; |
| struct syna_tcm_app_info *app_info; |
| struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; |
| |
| #ifdef USE_DEFAULT_TOUCH_REPORT_CONFIG |
| return 0; |
| #endif |
| |
| app_info = &tcm_hcd->app_info; |
| length = le2_to_uint(app_info->max_touch_report_config_size); |
| |
| if (length < TOUCH_REPORT_CONFIG_SIZE) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Invalid maximum touch report config size\n"); |
| return -EINVAL; |
| } |
| |
| LOCK_BUFFER(touch_hcd->out); |
| |
| retval = syna_tcm_alloc_mem(tcm_hcd, |
| &touch_hcd->out, |
| length); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to allocate memory for touch_hcd->out.buf\n"); |
| UNLOCK_BUFFER(touch_hcd->out); |
| return retval; |
| } |
| |
| idx = 0; |
| #ifdef WAKEUP_GESTURE |
| touch_hcd->out.buf[idx++] = TOUCH_GESTURE_DOUBLE_TAP; |
| touch_hcd->out.buf[idx++] = 8; |
| #endif |
| touch_hcd->out.buf[idx++] = TOUCH_FOREACH_ACTIVE_OBJECT; |
| touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_INDEX; |
| touch_hcd->out.buf[idx++] = 4; |
| touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_CLASSIFICATION; |
| touch_hcd->out.buf[idx++] = 4; |
| touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_X_POSITION; |
| touch_hcd->out.buf[idx++] = 12; |
| touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_Y_POSITION; |
| touch_hcd->out.buf[idx++] = 12; |
| touch_hcd->out.buf[idx++] = TOUCH_FOREACH_END; |
| touch_hcd->out.buf[idx++] = TOUCH_END; |
| |
| LOCK_BUFFER(touch_hcd->resp); |
| |
| retval = tcm_hcd->write_message(tcm_hcd, |
| CMD_SET_TOUCH_REPORT_CONFIG, |
| touch_hcd->out.buf, |
| length, |
| &touch_hcd->resp.buf, |
| &touch_hcd->resp.buf_size, |
| &touch_hcd->resp.data_length, |
| NULL, |
| 0); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to write command %s\n", |
| STR(CMD_SET_TOUCH_REPORT_CONFIG)); |
| UNLOCK_BUFFER(touch_hcd->resp); |
| UNLOCK_BUFFER(touch_hcd->out); |
| return retval; |
| } |
| |
| UNLOCK_BUFFER(touch_hcd->resp); |
| UNLOCK_BUFFER(touch_hcd->out); |
| |
| return 0; |
| } |
| |
| /** |
| * touch_check_input_params() - Check input parameters |
| * |
| * Check if any of the input parameters registered with the input subsystem for |
| * the input device has changed. |
| */ |
| static int touch_check_input_params(void) |
| { |
| unsigned int size; |
| struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; |
| |
| if (touch_hcd->max_x == 0 && touch_hcd->max_y == 0) |
| return 0; |
| |
| if (touch_hcd->input_params.max_objects != touch_hcd->max_objects) { |
| kfree(touch_hcd->touch_data.object_data); |
| size = sizeof(*touch_hcd->touch_data.object_data); |
| size *= touch_hcd->max_objects; |
| touch_hcd->touch_data.object_data = kzalloc(size, GFP_KERNEL); |
| if (!touch_hcd->touch_data.object_data) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to allocate memory for object_data\n"); |
| return -ENOMEM; |
| } |
| return 1; |
| } |
| |
| if (touch_hcd->input_params.max_x != touch_hcd->max_x) |
| return 1; |
| |
| if (touch_hcd->input_params.max_y != touch_hcd->max_y) |
| return 1; |
| |
| return 0; |
| } |
| |
| /** |
| * touch_set_input_reporting() - Configure touch report and set up new input |
| * device if necessary |
| * |
| * After a device reset event, configure the touch report and set up a new input |
| * device if any of the input parameters has changed after the device reset. |
| */ |
| static int touch_set_input_reporting(void) |
| { |
| int retval; |
| struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; |
| |
| if (tcm_hcd->id_info.mode != MODE_APPLICATION || |
| tcm_hcd->app_status != APP_STATUS_OK) { |
| LOGN(tcm_hcd->pdev->dev.parent, |
| "Application firmware not running\n"); |
| return 0; |
| } |
| |
| touch_hcd->report_touch = false; |
| |
| touch_free_objects(); |
| |
| mutex_lock(&touch_hcd->report_mutex); |
| |
| retval = touch_set_report_config(); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to set report config\n"); |
| goto exit; |
| } |
| |
| retval = touch_get_input_params(); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get input parameters\n"); |
| goto exit; |
| } |
| |
| retval = touch_check_input_params(); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to check input parameters\n"); |
| goto exit; |
| } else if (retval == 0) { |
| LOGD(tcm_hcd->pdev->dev.parent, |
| "Input parameters unchanged\n"); |
| goto exit; |
| } |
| |
| if (touch_hcd->input_dev != NULL) { |
| input_unregister_device(touch_hcd->input_dev); |
| touch_hcd->input_dev = NULL; |
| } |
| |
| retval = touch_set_input_dev(); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to set up input device\n"); |
| goto exit; |
| } |
| |
| exit: |
| mutex_unlock(&touch_hcd->report_mutex); |
| |
| touch_hcd->report_touch = retval < 0 ? false : true; |
| |
| return retval; |
| } |
| |
| static int touch_init(struct syna_tcm_hcd *tcm_hcd) |
| { |
| int retval; |
| |
| touch_hcd = kzalloc(sizeof(*touch_hcd), GFP_KERNEL); |
| if (!touch_hcd) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to allocate memory for touch_hcd\n"); |
| return -ENOMEM; |
| } |
| |
| touch_hcd->tcm_hcd = tcm_hcd; |
| |
| mutex_init(&touch_hcd->report_mutex); |
| |
| INIT_BUFFER(touch_hcd->out, false); |
| INIT_BUFFER(touch_hcd->resp, false); |
| |
| retval = touch_set_input_reporting(); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to set up input reporting\n"); |
| goto err_set_input_reporting; |
| } |
| |
| tcm_hcd->report_touch = touch_report; |
| |
| return 0; |
| |
| err_set_input_reporting: |
| kfree(touch_hcd->touch_data.object_data); |
| kfree(touch_hcd->prev_status); |
| |
| RELEASE_BUFFER(touch_hcd->resp); |
| RELEASE_BUFFER(touch_hcd->out); |
| |
| kfree(touch_hcd); |
| touch_hcd = NULL; |
| |
| return retval; |
| } |
| |
| static int touch_remove(struct syna_tcm_hcd *tcm_hcd) |
| { |
| if (!touch_hcd) |
| goto exit; |
| |
| tcm_hcd->report_touch = NULL; |
| |
| if (touch_hcd->input_dev) |
| input_unregister_device(touch_hcd->input_dev); |
| |
| kfree(touch_hcd->touch_data.object_data); |
| kfree(touch_hcd->prev_status); |
| |
| RELEASE_BUFFER(touch_hcd->resp); |
| RELEASE_BUFFER(touch_hcd->out); |
| |
| kfree(touch_hcd); |
| touch_hcd = NULL; |
| |
| exit: |
| complete(&touch_remove_complete); |
| |
| return 0; |
| } |
| |
| static int touch_syncbox(struct syna_tcm_hcd *tcm_hcd) |
| { |
| if (!touch_hcd) |
| return 0; |
| |
| switch (tcm_hcd->report.id) { |
| case REPORT_IDENTIFY: |
| touch_free_objects(); |
| break; |
| case REPORT_TOUCH: |
| if (!touch_hcd->suspend_touch) |
| touch_report(); |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int touch_asyncbox(struct syna_tcm_hcd *tcm_hcd) |
| { |
| int retval; |
| |
| if (!touch_hcd) |
| return 0; |
| |
| switch (tcm_hcd->async_report_id) { |
| case REPORT_IDENTIFY: |
| if (tcm_hcd->id_info.mode != MODE_APPLICATION) |
| break; |
| retval = tcm_hcd->identify(tcm_hcd, false); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to do identification\n"); |
| return retval; |
| } |
| retval = touch_set_input_reporting(); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to set up input reporting\n"); |
| return retval; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int touch_reset(struct syna_tcm_hcd *tcm_hcd) |
| { |
| int retval; |
| |
| if (!touch_hcd) { |
| retval = touch_init(tcm_hcd); |
| return retval; |
| } |
| |
| if (tcm_hcd->id_info.mode == MODE_APPLICATION) { |
| retval = touch_set_input_reporting(); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to set up input reporting\n"); |
| return retval; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int touch_early_suspend(struct syna_tcm_hcd *tcm_hcd) |
| { |
| if (!touch_hcd) |
| return 0; |
| |
| #ifdef WAKEUP_GESTURE |
| touch_hcd->suspend_touch = false; |
| #else |
| touch_hcd->suspend_touch = true; |
| #endif |
| |
| touch_free_objects(); |
| |
| return 0; |
| } |
| |
| static int touch_suspend(struct syna_tcm_hcd *tcm_hcd) |
| { |
| #ifdef WAKEUP_GESTURE |
| int retval; |
| #endif |
| |
| if (!touch_hcd) |
| return 0; |
| |
| touch_hcd->suspend_touch = true; |
| |
| touch_free_objects(); |
| |
| #ifdef WAKEUP_GESTURE |
| if (!touch_hcd->irq_wake) { |
| enable_irq_wake(tcm_hcd->irq); |
| touch_hcd->irq_wake = true; |
| } |
| |
| touch_hcd->suspend_touch = false; |
| |
| retval = tcm_hcd->set_dynamic_config(tcm_hcd, |
| DC_IN_WAKEUP_GESTURE_MODE, |
| 1); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to enable wakeup gesture mode\n"); |
| return retval; |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static int touch_resume(struct syna_tcm_hcd *tcm_hcd) |
| { |
| #ifdef WAKEUP_GESTURE |
| int retval; |
| #endif |
| |
| if (!touch_hcd) |
| return 0; |
| |
| touch_hcd->suspend_touch = false; |
| |
| #ifdef WAKEUP_GESTURE |
| if (touch_hcd->irq_wake) { |
| disable_irq_wake(tcm_hcd->irq); |
| touch_hcd->irq_wake = false; |
| } |
| |
| retval = tcm_hcd->set_dynamic_config(tcm_hcd, |
| DC_IN_WAKEUP_GESTURE_MODE, |
| 0); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to disable wakeup gesture mode\n"); |
| return retval; |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static struct syna_tcm_module_cb touch_module = { |
| .type = TCM_TOUCH, |
| .init = touch_init, |
| .remove = touch_remove, |
| .syncbox = touch_syncbox, |
| .asyncbox = touch_asyncbox, |
| .reset = touch_reset, |
| .suspend = touch_suspend, |
| .resume = touch_resume, |
| .early_suspend = touch_early_suspend, |
| }; |
| |
| static int __init touch_module_init(void) |
| { |
| return syna_tcm_add_module(&touch_module, true); |
| } |
| |
| static void __exit touch_module_exit(void) |
| { |
| syna_tcm_add_module(&touch_module, false); |
| |
| wait_for_completion(&touch_remove_complete); |
| } |
| |
| module_init(touch_module_init); |
| module_exit(touch_module_exit); |
| |
| MODULE_AUTHOR("Synaptics, Inc."); |
| MODULE_DESCRIPTION("Synaptics TCM Touch Module"); |
| MODULE_LICENSE("GPL v2"); |