// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #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 gesture_id { NO_GESTURE_DETECTED = 0, GESTURE_DOUBLE_TAP = 0X01, }; 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_ID, 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_FACE_DETECT, TOUCH_GESTURE_DATA, TOUCH_OBJECT_N_FORCE, 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_id; 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; unsigned int fd_data; unsigned int force_data; }; 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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to get object classification\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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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_OBJECT_N_FORCE: bits = config_data[idx++]; retval = touch_get_report_data(offset, bits, &data); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to get object force\n"); return retval; } touch_data->force_data = data; offset += bits; break; case TOUCH_0D_BUTTONS_STATE: bits = config_data[idx++]; retval = touch_get_report_data(offset, bits, &data); if (retval < 0) { LOG_ERR(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_ID: bits = config_data[idx++]; retval = touch_get_report_data(offset, bits, &data); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to get gesture double tap\n"); return retval; } touch_data->gesture_id = data; offset += bits; break; case TOUCH_FRAME_RATE: bits = config_data[idx++]; retval = touch_get_report_data(offset, bits, &data); if (retval < 0) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to get NSM state\n"); return retval; } touch_data->nsm_state = data; offset += bits; break; case TOUCH_GESTURE_DATA: bits = config_data[idx++]; offset += bits; break; case TOUCH_NUM_OF_ACTIVE_OBJECTS: bits = config_data[idx++]; retval = touch_get_report_data(offset, bits, &data); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to get number of active 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) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to get number of CPU cycles used since last frame\n"); return retval; } touch_data->num_of_cpu_cycles = data; offset += bits; break; case TOUCH_FACE_DETECT: bits = config_data[idx++]; retval = touch_get_report_data(offset, bits, &data); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to detect face\n"); return retval; } touch_data->fd_data = 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; default: 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) { LOG_ERR(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_id == 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); #ifdef TYPE_B_PROTOCOL input_mt_init_slots(touch_hcd->input_dev, touch_hcd->max_objects, INPUT_MT_DIRECT); #endif 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) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for touch_hcd->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; /* Get resolution from fw */ 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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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_ID; 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) { LOG_ERR(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) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for touch_hcd->touch_data.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"); if (tcm_hcd->id_info.mode != MODE_HOST_DOWNLOAD) return 0; } touch_hcd->report_touch = false; touch_free_objects(); mutex_lock(&touch_hcd->report_mutex); retval = touch_set_report_config(); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to set report config\n"); goto exit; } retval = touch_get_input_params(); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to get input parameters\n"); goto exit; } retval = touch_check_input_params(); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to check input parameters\n"); goto exit; } else if (retval == 0) { LOGN(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(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) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do identification\n"); return retval; } retval = touch_set_input_reporting(); if (retval < 0) { LOG_ERR(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 || tcm_hcd->id_info.mode == MODE_HOST_DOWNLOAD) { retval = touch_set_input_reporting(); if (retval < 0) { LOG_ERR(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) { #ifdef WAKEUP_GESTURE int retval; #endif if (!touch_hcd) return 0; #ifdef WAKEUP_GESTURE touch_hcd->suspend_touch = false; #else touch_hcd->suspend_touch = true; #endif 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) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to enable wakeup gesture mode\n"); return retval; } #endif return 0; } static int touch_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_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) { LOG_ERR(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");