// SPDX-License-Identifier: GPL-2.0 /* * Synaptics TouchCom touchscreen driver * * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. * * 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. */ /** * @file syna_tcm2.c * * This file implements the Synaptics device driver running under Linux kernel * input device subsystem, and also communicate with Synaptics touch controller * through TouchComm command-response protocol. */ #include "syna_tcm2.h" #include "syna_tcm2_platform.h" #include "synaptics_touchcom_core_dev.h" #include "synaptics_touchcom_func_base.h" #include "synaptics_touchcom_func_touch.h" #ifdef STARTUP_REFLASH #include "synaptics_touchcom_func_reflash.h" #endif #ifdef MULTICHIP_DUT_REFLASH #include "synaptics_touchcom_func_romboot.h" #endif #if defined(USE_DRM_BRIDGE) #include #include #endif /** * @section: USE_CUSTOM_TOUCH_REPORT_CONFIG * Open if willing to set up the format of touch report. * The custom_touch_format[] array can be used to describe the * customized report format. */ #ifdef USE_CUSTOM_TOUCH_REPORT_CONFIG static unsigned char custom_touch_format[] = { /* entity code */ /* bits */ #ifdef ENABLE_WAKEUP_GESTURE TOUCH_REPORT_GESTURE_ID, 8, #endif TOUCH_REPORT_NUM_OF_ACTIVE_OBJECTS, 8, TOUCH_REPORT_FOREACH_ACTIVE_OBJECT, TOUCH_REPORT_OBJECT_N_INDEX, 8, TOUCH_REPORT_OBJECT_N_CLASSIFICATION, 8, TOUCH_REPORT_OBJECT_N_X_POSITION, 16, TOUCH_REPORT_OBJECT_N_Y_POSITION, 16, TOUCH_REPORT_FOREACH_END, TOUCH_REPORT_END }; #endif /** * @section: RESET_ON_RESUME_DELAY_MS * The delayed time to issue a reset on resume state. * This configuration depends on RESET_ON_RESUME. */ #ifdef RESET_ON_RESUME #define RESET_ON_RESUME_DELAY_MS (100) #endif /** * @section: POWER_ALIVE_AT_SUSPEND * indicate that the power is still alive even at * system suspend. * otherwise, there is no power supplied when system * is going to suspend stage. */ #define POWER_ALIVE_AT_SUSPEND /** * @section: global variables for an active drm panel * in order to register display notifier */ #ifdef USE_DRM_PANEL_NOTIFIER struct drm_panel *active_panel; #endif /** * syna_dev_enable_lowpwr_gesture() * * Enable or disable the low power gesture mode. * Furthermore, set up the wake-up irq. * * @param * [ in] tcm: tcm driver handle * [ in] en: '1' to enable low power gesture mode; '0' to disable * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_enable_lowpwr_gesture(struct syna_tcm *tcm, bool en) { int retval = 0; struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn; if (!tcm->lpwg_enabled) return 0; if (attn->irq_id == 0) return 0; if (en) { if (!tcm->irq_wake) { enable_irq_wake(attn->irq_id); tcm->irq_wake = true; } /* enable wakeup gesture mode * * the wakeup gesture control may result from by finger event; * therefore, it's better to use ATTN driven mode here */ retval = syna_tcm_set_dynamic_config(tcm->tcm_dev, DC_ENABLE_WAKEUP_GESTURE_MODE, 1, RESP_IN_ATTN); if (retval < 0) { LOGE("Fail to enable wakeup gesture via DC command\n"); return retval; } } else { if (tcm->irq_wake) { disable_irq_wake(attn->irq_id); tcm->irq_wake = false; } /* disable wakeup gesture mode * * the wakeup gesture control may result from by finger event; * therefore, it's better to use ATTN driven mode here */ retval = syna_tcm_set_dynamic_config(tcm->tcm_dev, DC_ENABLE_WAKEUP_GESTURE_MODE, 0, RESP_IN_ATTN); if (retval < 0) { LOGE("Fail to disable wakeup gesture via DC command\n"); return retval; } } return retval; } #ifdef ENABLE_CUSTOM_TOUCH_ENTITY /** * @section: Callback function used for custom entity parsing in touch report * * Allow to parse the custom "newly" entity in the touch report. * Please note that this function will be invoked in ISR * * @param * [ in] code: the code of current touch entity * [ in] config: the report configuration stored * [in/out] config_offset: offset of current position in report config, * and then return the updated position. * [ in] report: touch report given * [in/out] report_offset: offset of current position in touch report, * the updated position should be returned. * [ in] report_size: size of given touch report * [ in] callback_data: pointer to caller data passed to callback function * * @return * on success, 0 or positive value; otherwise, negative value on error. */ static int syna_dev_parse_custom_touch_report(const unsigned char code, const unsigned char *config, unsigned int *config_offset, const unsigned char *report, unsigned int *report_offset, unsigned int report_size, void *callback_data) { /** * sample code to demonstrate how to parse the custom entity * from the report, additional modifications must be beeded. * * struct syna_tcm *tcm = (struct syna_tcm *)callback_data; * unsigned int data; * unsigned int bits; * * switch (code) { * case CUSTOM_ENTITY_CODE: * bits = config[(*config_offset)++]; * syna_tcm_get_touch_data(report, report_size, * *report_offset, bits, &data); * * *report_offset += bits; * return bits; * default: * LOGW("Unknown touch config code (idx:%d 0x%02x)\n", * *config_offset, code); * return (-EINVAL); * } * */ return (-EINVAL); } #endif /** * syna_tcm_free_input_events() * * Clear all relevant touched events. * * @param * [ in] tcm: the driver handle * * @return * none. */ static void syna_dev_free_input_events(struct syna_tcm *tcm) { struct input_dev *input_dev = tcm->input_dev; #ifdef TYPE_B_PROTOCOL unsigned int idx; #endif if (input_dev == NULL) return; syna_pal_mutex_lock(&tcm->tp_event_mutex); #ifdef TYPE_B_PROTOCOL for (idx = 0; idx < MAX_NUM_OBJECTS; idx++) { input_mt_slot(input_dev, idx); input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0); } #endif input_report_key(input_dev, BTN_TOUCH, 0); input_report_key(input_dev, BTN_TOOL_FINGER, 0); #ifndef TYPE_B_PROTOCOL input_mt_sync(input_dev); #endif input_sync(input_dev); syna_pal_mutex_unlock(&tcm->tp_event_mutex); } /** * syna_dev_report_input_events() * * Report touched events to the input subsystem. * * After syna_tcm_get_event_data() function and the touched data is ready, * this function can be called to report an input event. * * @param * [ in] tcm: the driver handle * * @return * none. */ static void syna_dev_report_input_events(struct syna_tcm *tcm) { unsigned int idx; unsigned int x; unsigned int y; int wx; int wy; unsigned int status; unsigned int touch_count; struct input_dev *input_dev = tcm->input_dev; unsigned int max_objects = tcm->tcm_dev->max_objects; struct tcm_touch_data_blob *touch_data; struct tcm_objects_data_blob *object_data; if (input_dev == NULL) return; syna_pal_mutex_lock(&tcm->tp_event_mutex); touch_data = &tcm->tp_data; object_data = &tcm->tp_data.object_data[0]; #ifdef ENABLE_WAKEUP_GESTURE if ((tcm->pwr_state == LOW_PWR) && tcm->irq_wake) { if (touch_data->gesture_id) { LOGD("Gesture detected, id:%d\n", touch_data->gesture_id); input_report_key(input_dev, KEY_WAKEUP, 1); input_sync(input_dev); input_report_key(input_dev, KEY_WAKEUP, 0); input_sync(input_dev); } } #endif if (tcm->pwr_state == LOW_PWR) goto exit; touch_count = 0; for (idx = 0; idx < max_objects; idx++) { if (tcm->prev_obj_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(input_dev, idx); input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0); #endif break; case FINGER: case GLOVED_OBJECT: x = object_data[idx].x_pos; y = object_data[idx].y_pos; wx = object_data[idx].x_width; wy = object_data[idx].y_width; #ifdef REPORT_SWAP_XY x = x ^ y; y = x ^ y; x = x ^ y; #endif #ifdef REPORT_FLIP_X x = tcm->input_dev_params.max_x - x; #endif #ifdef REPORT_FLIP_Y y = tcm->input_dev_params.max_y - y; #endif #ifdef TYPE_B_PROTOCOL input_mt_slot(input_dev, idx); input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 1); #endif input_report_key(input_dev, BTN_TOUCH, 1); input_report_key(input_dev, BTN_TOOL_FINGER, 1); input_report_abs(input_dev, ABS_MT_POSITION_X, x); input_report_abs(input_dev, ABS_MT_POSITION_Y, y); #ifdef REPORT_TOUCH_WIDTH input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, MAX(wx, wy)); input_report_abs(input_dev, ABS_MT_TOUCH_MINOR, MIN(wx, wy)); #endif #ifndef TYPE_B_PROTOCOL input_mt_sync(input_dev); #endif LOGD("Finger %d: x = %d, y = %d\n", idx, x, y); touch_count++; break; default: break; } tcm->prev_obj_status[idx] = object_data[idx].status; } if (touch_count == 0) { input_report_key(input_dev, BTN_TOUCH, 0); input_report_key(input_dev, BTN_TOOL_FINGER, 0); #ifndef TYPE_B_PROTOCOL input_mt_sync(input_dev); #endif } input_sync(input_dev); exit: syna_pal_mutex_unlock(&tcm->tp_event_mutex); } /** * syna_dev_create_input_device() * * Allocate an input device and set up relevant parameters to the * input subsystem. * * @param * [ in] tcm: the driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_create_input_device(struct syna_tcm *tcm) { int retval = 0; struct tcm_dev *tcm_dev = tcm->tcm_dev; struct input_dev *input_dev = NULL; #ifdef DEV_MANAGED_API struct device *dev = syna_request_managed_device(); if (!dev) { LOGE("Invalid managed device\n"); return -EINVAL; } input_dev = devm_input_allocate_device(dev); #else /* Legacy API */ input_dev = input_allocate_device(); #endif if (input_dev == NULL) { LOGE("Fail to allocate input device\n"); return -ENODEV; } input_dev->name = TOUCH_INPUT_NAME; input_dev->phys = TOUCH_INPUT_PHYS_PATH; input_dev->id.product = SYNAPTICS_TCM_DRIVER_ID; input_dev->id.version = SYNAPTICS_TCM_DRIVER_VERSION; input_dev->dev.parent = tcm->pdev->dev.parent; input_set_drvdata(input_dev, tcm); set_bit(EV_SYN, input_dev->evbit); set_bit(EV_KEY, input_dev->evbit); set_bit(EV_ABS, input_dev->evbit); set_bit(BTN_TOUCH, input_dev->keybit); set_bit(BTN_TOOL_FINGER, input_dev->keybit); #ifdef INPUT_PROP_DIRECT set_bit(INPUT_PROP_DIRECT, input_dev->propbit); #endif #ifdef ENABLE_WAKEUP_GESTURE set_bit(KEY_WAKEUP, input_dev->keybit); input_set_capability(input_dev, EV_KEY, KEY_WAKEUP); #endif input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, tcm_dev->max_x, 0, 0); input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, tcm_dev->max_y, 0, 0); input_mt_init_slots(input_dev, tcm_dev->max_objects, INPUT_MT_DIRECT); #ifdef REPORT_TOUCH_WIDTH input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0); #endif tcm->input_dev_params.max_x = tcm_dev->max_x; tcm->input_dev_params.max_y = tcm_dev->max_y; tcm->input_dev_params.max_objects = tcm_dev->max_objects; retval = input_register_device(input_dev); if (retval < 0) { LOGE("Fail to register input device\n"); input_free_device(input_dev); input_dev = NULL; return retval; } tcm->input_dev = input_dev; return 0; } /** * syna_dev_release_input_device() * * Release an input device allocated previously. * * @param * [ in] tcm: the driver handle * * @return * none. */ static void syna_dev_release_input_device(struct syna_tcm *tcm) { if (!tcm->input_dev) return; input_unregister_device(tcm->input_dev); input_free_device(tcm->input_dev); tcm->input_dev = NULL; } /** * syna_dev_check_input_params() * * Check if any of the input parameters registered to the input subsystem * has changed. * * @param * [ in] tcm: the driver handle * * @return * positive value to indicate mismatching parameters; otherwise, return 0. */ static int syna_dev_check_input_params(struct syna_tcm *tcm) { struct tcm_dev *tcm_dev = tcm->tcm_dev; if (tcm_dev->max_x == 0 && tcm_dev->max_y == 0) return 0; if (tcm->input_dev_params.max_x != tcm_dev->max_x) return 1; if (tcm->input_dev_params.max_y != tcm_dev->max_y) return 1; if (tcm->input_dev_params.max_objects != tcm_dev->max_objects) return 1; if (tcm_dev->max_objects > MAX_NUM_OBJECTS) { LOGW("Out of max num objects defined, in app_info: %d\n", tcm_dev->max_objects); return 0; } LOGN("Input parameters unchanged\n"); return 0; } /** * syna_dev_set_up_input_device() * * Set up input device to the input subsystem by confirming the supported * parameters and creating the device. * * @param * [ in] tcm: the driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_set_up_input_device(struct syna_tcm *tcm) { int retval = 0; if (IS_NOT_APP_FW_MODE(tcm->tcm_dev->dev_mode)) { LOGN("Application firmware not running, current mode: %02x\n", tcm->tcm_dev->dev_mode); return 0; } syna_dev_free_input_events(tcm); syna_pal_mutex_lock(&tcm->tp_event_mutex); retval = syna_dev_check_input_params(tcm); if (retval == 0) goto exit; if (tcm->input_dev != NULL) syna_dev_release_input_device(tcm); retval = syna_dev_create_input_device(tcm); if (retval < 0) { LOGE("Fail to create input device\n"); goto exit; } exit: syna_pal_mutex_unlock(&tcm->tp_event_mutex); return retval; } /** * syna_dev_isr() * * This is the function to be called when the interrupt is asserted. * The purposes of this handler is to read events generated by device and * retrieve all enqueued messages until ATTN is no longer asserted. * * @param * [ in] irq: interrupt line * [ in] data: private data being passed to the handler function * * @return * on success, 0; otherwise, negative value on error. */ static irqreturn_t syna_dev_isr(int irq, void *data) { int retval; unsigned char code = 0; struct syna_tcm *tcm = data; struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn; /* It is possible that interrupts were disabled while the handler is * executing, before acquiring the mutex. If so, simply return. */ if (syna_set_bus_ref(tcm, SYNA_BUS_REF_IRQ, true) < 0) goto exit; if (unlikely(gpio_get_value(attn->irq_gpio) != attn->irq_on_state)) goto exit; tcm->isr_pid = current->pid; #ifdef HAS_SYSFS_INTERFACE if (tcm->is_attn_redirecting) { syna_cdev_redirect_attn(tcm); goto exit; } #endif /* retrieve the original report date generated by firmware */ retval = syna_tcm_get_event_data(tcm->tcm_dev, &code, &tcm->event_data); if (retval < 0) { LOGE("Fail to get event data\n"); goto exit; } #ifdef ENABLE_EXTERNAL_FRAME_PROCESS if (tcm->report_to_queue[code] == EFP_ENABLE) { syna_tcm_buf_lock(&tcm->tcm_dev->external_buf); syna_cdev_update_report_queue(tcm, code, &tcm->tcm_dev->external_buf); syna_tcm_buf_unlock(&tcm->tcm_dev->external_buf); #ifndef REPORT_CONCURRENTLY goto exit; #endif } #endif /* report input event only when receiving a touch report */ if (code == REPORT_TOUCH) { /* parse touch report once received */ retval = syna_tcm_parse_touch_report(tcm->tcm_dev, tcm->event_data.buf, tcm->event_data.data_length, &tcm->tp_data); if (retval < 0) { LOGE("Fail to parse touch report\n"); goto exit; } /* forward the touch event to system */ syna_dev_report_input_events(tcm); } exit: syna_set_bus_ref(tcm, SYNA_BUS_REF_IRQ, false); return IRQ_HANDLED; } /** * syna_dev_request_irq() * * Allocate an interrupt line and register the ISR handler * * @param * [ in] tcm: the driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_request_irq(struct syna_tcm *tcm) { int retval; struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn; #ifdef DEV_MANAGED_API struct device *dev = syna_request_managed_device(); if (!dev) { LOGE("Invalid managed device\n"); retval = -EINVAL; goto exit; } #endif if (attn->irq_gpio < 0) { LOGE("Invalid IRQ GPIO\n"); retval = -EINVAL; goto exit; } attn->irq_id = gpio_to_irq(attn->irq_gpio); #ifdef DEV_MANAGED_API retval = devm_request_threaded_irq(dev, attn->irq_id, NULL, syna_dev_isr, attn->irq_flags, PLATFORM_DRIVER_NAME, tcm); #else /* Legacy API */ retval = request_threaded_irq(attn->irq_id, NULL, syna_dev_isr, attn->irq_flags, PLATFORM_DRIVER_NAME, tcm); #endif if (retval < 0) { LOGE("Fail to request threaded irq\n"); goto exit; } attn->irq_enabled = true; LOGI("Interrupt handler registered\n"); exit: return retval; } /** * syna_dev_release_irq() * * Release an interrupt line allocated previously * * @param * [ in] tcm: the driver handle * * @return * none. */ static void syna_dev_release_irq(struct syna_tcm *tcm) { struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn; #ifdef DEV_MANAGED_API struct device *dev = syna_request_managed_device(); if (!dev) { LOGE("Invalid managed device\n"); return; } #endif if (attn->irq_id <= 0) return; if (tcm->hw_if->ops_enable_irq) tcm->hw_if->ops_enable_irq(tcm->hw_if, false); #ifdef DEV_MANAGED_API devm_free_irq(dev, attn->irq_id, tcm); #else free_irq(attn->irq_id, tcm); #endif attn->irq_id = 0; attn->irq_enabled = false; LOGI("Interrupt handler released\n"); } /** * syna_dev_set_up_app_fw() * * Implement the essential steps for the initialization including the * preparation of app info and the configuration of touch report. * * This function should be called whenever the device initially powers * up, resets, or firmware update. * * @param * [ in] tcm: tcm driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_set_up_app_fw(struct syna_tcm *tcm) { int retval = 0; struct tcm_dev *tcm_dev = tcm->tcm_dev; if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { LOGN("Application firmware not running, current mode: %02x\n", tcm_dev->dev_mode); return -EINVAL; } /* collect app info containing most of sensor information */ retval = syna_tcm_get_app_info(tcm_dev, &tcm_dev->app_info); if (retval < 0) { LOGE("Fail to get application info\n"); return -EIO; } /* set up the format of touch report */ #ifdef USE_CUSTOM_TOUCH_REPORT_CONFIG retval = syna_tcm_set_touch_report_config(tcm_dev, custom_touch_format, (unsigned int)sizeof(custom_touch_format)); if (retval < 0) { LOGE("Fail to setup the custom touch report format\n"); return -EIO; } #endif /* preserve the format of touch report */ retval = syna_tcm_preserve_touch_report_config(tcm_dev); if (retval < 0) { LOGE("Fail to preserve touch report config\n"); return -EIO; } #ifdef ENABLE_CUSTOM_TOUCH_ENTITY /* set up custom touch data parsing method */ retval = syna_tcm_set_custom_touch_data_parsing_callback(tcm_dev, syna_dev_parse_custom_touch_report, (void *)tcm); if (retval < 0) { LOGE("Fail to set up custom touch data parsing method\n"); return -EIO; } #endif return retval; } /** * syna_dev_reflash_startup_work() * * Perform firmware update during system startup. * Function is available when the 'STARTUP_REFLASH' configuration * is enabled. * * @param * [ in] work: handle of work structure * * @return * none. */ #ifdef STARTUP_REFLASH static void syna_dev_reflash_startup_work(struct work_struct *work) { int retval; struct delayed_work *delayed_work; struct syna_tcm *tcm; struct tcm_dev *tcm_dev; const struct firmware *fw_entry; const unsigned char *fw_image = NULL; unsigned int fw_image_size; delayed_work = container_of(work, struct delayed_work, work); tcm = container_of(delayed_work, struct syna_tcm, reflash_work); tcm_dev = tcm->tcm_dev; /* get firmware image */ retval = request_firmware(&fw_entry, tcm->hw_if->fw_name, tcm->pdev->dev.parent); if (retval < 0) { LOGE("Fail to request %s\n", tcm->hw_if->fw_name); return; } fw_image = fw_entry->data; fw_image_size = fw_entry->size; LOGD("Firmware image size = %d\n", fw_image_size); pm_stay_awake(&tcm->pdev->dev); if (syna_set_bus_ref(tcm, SYNA_BUS_REF_FW_UPDATE, true) < 0) goto exit; /* perform fw update */ #ifdef MULTICHIP_DUT_REFLASH /* do firmware update for the multichip-based device */ retval = syna_tcm_romboot_do_multichip_reflash(tcm_dev, fw_image, fw_image_size, RESP_IN_ATTN, false); #else /* do firmware update for the common device */ retval = syna_tcm_do_fw_update(tcm_dev, fw_image, fw_image_size, RESP_IN_ATTN, false); #endif if (retval < 0) { LOGE("Fail to do reflash\n"); goto exit; } /* re-initialize the app fw */ retval = syna_dev_set_up_app_fw(tcm); if (retval < 0) { LOGE("Fail to set up app fw after fw update\n"); goto exit; } /* check and re-create the input device if needed */ retval = syna_dev_set_up_input_device(tcm); if (retval < 0) { LOGE("Fail to register input device\n"); goto exit; } exit: syna_set_bus_ref(tcm, SYNA_BUS_REF_FW_UPDATE, false); pm_relax(&tcm->pdev->dev); } #endif /** * syna_dev_enter_normal_sensing() * * Helper to enter normal seneing mode * * @param * [ in] tcm: tcm driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_enter_normal_sensing(struct syna_tcm *tcm) { int retval = 0; if (!tcm) return -EINVAL; /* bring out of sleep mode. */ retval = syna_tcm_sleep(tcm->tcm_dev, false); if (retval < 0) { LOGE("Fail to exit deep sleep\n"); return retval; } /* disable low power gesture mode, if needed */ if (tcm->lpwg_enabled) { retval = syna_dev_enable_lowpwr_gesture(tcm, false); if (retval < 0) { LOGE("Fail to disable low power gesture mode\n"); return retval; } } tcm->pwr_state = PWR_ON; return 0; } /** * syna_dev_enter_lowpwr_sensing() * * Helper to enter power-saved sensing mode, that * may be the lower power gesture mode or deep sleep mode. * * @param * [ in] tcm: tcm driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_enter_lowpwr_sensing(struct syna_tcm *tcm) { int retval = 0; if (!tcm) return -EINVAL; /* enable low power gesture mode, if needed */ if (tcm->lpwg_enabled) { retval = syna_dev_enable_lowpwr_gesture(tcm, true); if (retval < 0) { LOGE("Fail to disable low power gesture mode\n"); return retval; } } else { /* enter sleep mode for non-LPWG cases */ if (!tcm->slept_in_early_suspend) { retval = syna_tcm_sleep(tcm->tcm_dev, true); if (retval < 0) { LOGE("Fail to enter deep sleep\n"); return retval; } } } tcm->pwr_state = LOW_PWR; return 0; } static int syna_pinctrl_configure(struct syna_tcm *tcm, bool enable) { struct pinctrl_state *state; if (IS_ERR_OR_NULL(tcm->pinctrl)) { LOGE("Invalid pinctrl!\n"); return -EINVAL; } LOGD("%s\n", enable ? "ACTIVE" : "SUSPEND"); if (enable) { state = pinctrl_lookup_state(tcm->pinctrl, "ts_active"); if (IS_ERR(state)) LOGE("Could not get ts_active pinstate!\n"); } else { state = pinctrl_lookup_state(tcm->pinctrl, "ts_suspend"); if (IS_ERR(state)) LOGE("Could not get ts_suspend pinstate!\n"); } if (!IS_ERR_OR_NULL(state)) return pinctrl_select_state(tcm->pinctrl, state); return 0; } /** * syna_dev_resume() * * Resume from the suspend state. * If RESET_ON_RESUME is defined, a reset is issued to the touch controller. * Otherwise, the touch controller is brought out of sleep mode. * * @param * [ in] dev: an instance of device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_resume(struct device *dev) { int retval; struct syna_tcm *tcm = dev_get_drvdata(dev); struct syna_hw_interface *hw_if = tcm->hw_if; bool irq_enabled = true; /* exit directly if device isn't in suspend state */ if (tcm->pwr_state == PWR_ON) return 0; LOGI("Prepare to resume device\n"); syna_pinctrl_configure(tcm, true); #ifdef RESET_ON_RESUME syna_pal_sleep_ms(RESET_ON_RESUME_DELAY_MS); if (hw_if->ops_hw_reset) { hw_if->ops_hw_reset(hw_if); } else { retval = syna_tcm_reset(tcm->tcm_dev); if (retval < 0) { LOGE("Fail to do reset\n"); goto exit; } } #else /* enter normal power mode */ retval = syna_dev_enter_normal_sensing(tcm); if (retval < 0) { LOGE("Fail to enter normal power mode\n"); goto exit; } retval = syna_tcm_rezero(tcm->tcm_dev); if (retval < 0) { LOGE("Fail to rezero\n"); goto exit; } #endif /* end of RESET_ON_RESUME */ LOGI("Prepare to set up application firmware\n"); /* set up app firmware */ retval = syna_dev_set_up_app_fw(tcm); if (retval < 0) { LOGE("Fail to set up app firmware on resume\n"); goto exit; } retval = 0; LOGI("Device resumed (pwr_state:%d)\n", tcm->pwr_state); exit: /* once lpwg is enabled, irq should be enabled already. * otherwise, set irq back to active mode. */ irq_enabled = (!tcm->lpwg_enabled); /* enable irq */ if (irq_enabled && (hw_if->ops_enable_irq)) hw_if->ops_enable_irq(hw_if, true); tcm->slept_in_early_suspend = false; complete_all(&tcm->bus_resumed); return retval; } /** * syna_dev_suspend() * * Put device into suspend state. * Enter either the lower power gesture mode or sleep mode. * * @param * [ in] dev: an instance of device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_suspend(struct device *dev) { #ifdef POWER_ALIVE_AT_SUSPEND int retval; #endif struct syna_tcm *tcm = dev_get_drvdata(dev); struct syna_hw_interface *hw_if = tcm->hw_if; bool irq_disabled = true; /* exit directly if device is already in suspend state */ if (tcm->pwr_state != PWR_ON) return 0; LOGI("Prepare to suspend device\n"); reinit_completion(&tcm->bus_resumed); /* clear all input events */ syna_dev_free_input_events(tcm); #ifdef POWER_ALIVE_AT_SUSPEND /* enter power saved mode if power is not off */ retval = syna_dev_enter_lowpwr_sensing(tcm); if (retval < 0) { LOGE("Fail to enter suspended power mode\n"); return retval; } #else tcm->pwr_state = PWR_OFF; #endif /* once lpwg is enabled, irq should be alive. * otherwise, disable irq in suspend. */ irq_disabled = (!tcm->lpwg_enabled); /* disable irq */ if (irq_disabled && (hw_if->ops_enable_irq)) hw_if->ops_enable_irq(hw_if, false); syna_pinctrl_configure(tcm, false); LOGI("Device suspended (pwr_state:%d)\n", tcm->pwr_state); return 0; } #if defined(ENABLE_DISP_NOTIFIER) /** * syna_dev_early_suspend() * * If having early suspend support, enter the sleep mode for * non-lpwg cases. * * @param * [ in] dev: an instance of device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_early_suspend(struct device *dev) { int retval; struct syna_tcm *tcm = dev_get_drvdata(dev); /* exit directly if device is already in suspend state */ if (tcm->pwr_state != PWR_ON) return 0; if (!tcm->lpwg_enabled) { retval = syna_tcm_sleep(tcm->tcm_dev, true); if (retval < 0) { LOGE("Fail to enter deep sleep\n"); return retval; } } tcm->slept_in_early_suspend = true; return 0; } /** * syna_dev_fb_notifier_cb() * * Listen the display screen on/off event and perform the corresponding * actions. * * @param * [ in] nb: instance of notifier_block * [ in] action: fb action * [ in] data: fb event data * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_fb_notifier_cb(struct notifier_block *nb, unsigned long action, void *data) { int retval; int transition; #if defined(USE_DRM_PANEL_NOTIFIER) struct drm_panel_notifier *evdata = data; #else struct fb_event *evdata = data; #endif struct syna_tcm *tcm = container_of(nb, struct syna_tcm, fb_notifier); int time = 0; int disp_blank_powerdown; int disp_early_event_blank; int disp_blank; int disp_blank_unblank; if (!evdata || !evdata->data || !tcm) return 0; retval = 0; #if defined(USE_DRM_PANEL_NOTIFIER) disp_blank_powerdown = DRM_PANEL_BLANK_POWERDOWN; disp_early_event_blank = DRM_PANEL_EARLY_EVENT_BLANK; disp_blank = DRM_PANEL_EVENT_BLANK; disp_blank_unblank = DRM_PANEL_BLANK_UNBLANK; #else disp_blank_powerdown = FB_BLANK_POWERDOWN; disp_early_event_blank = FB_EARLY_EVENT_BLANK; disp_blank = FB_EVENT_BLANK; disp_blank_unblank = FB_BLANK_UNBLANK; #endif transition = *(int *)evdata->data; /* confirm the firmware flashing is completed before screen off */ if (transition == disp_blank_powerdown) { while (ATOMIC_GET(tcm->tcm_dev->firmware_flashing)) { syna_pal_sleep_ms(500); time += 500; if (time >= 5000) { LOGE("Timed out waiting for reflashing\n"); ATOMIC_SET(tcm->tcm_dev->firmware_flashing, 0); return -EIO; } } } if (action == disp_early_event_blank && transition == disp_blank_powerdown) { retval = syna_dev_early_suspend(&tcm->pdev->dev); } else if (action == disp_blank) { if (transition == disp_blank_powerdown) { retval = syna_dev_suspend(&tcm->pdev->dev); tcm->fb_ready = 0; } else if (transition == disp_blank_unblank) { #ifndef RESUME_EARLY_UNBLANK retval = syna_dev_resume(&tcm->pdev->dev); tcm->fb_ready++; #endif } else if (action == disp_early_event_blank && transition == disp_blank_unblank) { #ifdef RESUME_EARLY_UNBLANK retval = syna_dev_resume(&tcm->pdev->dev); tcm->fb_ready++; #endif } } return 0; } #endif static void syna_suspend_work(struct work_struct *work) { struct syna_tcm *tcm = container_of(work, struct syna_tcm, suspend_work); syna_dev_suspend(&tcm->pdev->dev); } static void syna_resume_work(struct work_struct *work) { struct syna_tcm *tcm = container_of(work, struct syna_tcm, resume_work); syna_dev_resume(&tcm->pdev->dev); } void syna_aggregate_bus_state(struct syna_tcm *tcm) { /* Complete or cancel any outstanding transitions */ cancel_work_sync(&tcm->suspend_work); cancel_work_sync(&tcm->resume_work); if ((tcm->bus_refmask == 0 && tcm->pwr_state != PWR_ON) || (tcm->bus_refmask && tcm->pwr_state == PWR_ON)) return; if (tcm->bus_refmask == 0) queue_work(tcm->event_wq, &tcm->suspend_work); else queue_work(tcm->event_wq, &tcm->resume_work); } int syna_set_bus_ref(struct syna_tcm *tcm, u32 ref, bool enable) { int result = 0; mutex_lock(&tcm->bus_mutex); if ((enable && (tcm->bus_refmask & ref)) || (!enable && !(tcm->bus_refmask & ref))) { LOGD("reference is unexpectedly set: mask=0x%04X, ref=0x%04X, enable=%d\n", tcm->bus_refmask, ref, enable); mutex_unlock(&tcm->bus_mutex); return -EINVAL; } if (enable) { /* * IRQs can only keep the bus active. IRQs received while the * bus is transferred to AOC should be ignored. */ if (ref == SYNA_BUS_REF_IRQ && tcm->bus_refmask == 0) result = -EAGAIN; else tcm->bus_refmask |= ref; } else tcm->bus_refmask &= ~ref; syna_aggregate_bus_state(tcm); mutex_unlock(&tcm->bus_mutex); /* * When triggering a wake, wait up to one second to resume. SCREEN_ON * and IRQ references do not need to wait. */ if (enable && ref != SYNA_BUS_REF_SCREEN_ON && ref != SYNA_BUS_REF_IRQ) { wait_for_completion_timeout(&tcm->bus_resumed, HZ); if (tcm->pwr_state != PWR_ON) { LOGE("Failed to wake the touch bus.\n"); result = -ETIMEDOUT; } } return result; } #if defined(USE_DRM_BRIDGE) struct drm_connector *syna_get_bridge_connector(struct drm_bridge *bridge) { struct drm_connector *connector; struct drm_connector_list_iter conn_iter; drm_connector_list_iter_begin(bridge->dev, &conn_iter); drm_for_each_connector_iter(connector, &conn_iter) { if (connector->encoder == bridge->encoder) break; } drm_connector_list_iter_end(&conn_iter); return connector; } static bool syna_bridge_is_lp_mode(struct drm_connector *connector) { if (connector && connector->state) { struct exynos_drm_connector_state *s = to_exynos_connector_state(connector->state); return s->exynos_mode.is_lp_mode; } return false; } static void syna_panel_bridge_enable(struct drm_bridge *bridge) { struct syna_tcm *tcm = container_of(bridge, struct syna_tcm, panel_bridge); pr_debug("%s\n", __func__); if (!tcm ->is_panel_lp_mode) syna_set_bus_ref(tcm, SYNA_BUS_REF_SCREEN_ON, true); } static void syna_panel_bridge_disable(struct drm_bridge *bridge) { struct syna_tcm *tcm = container_of(bridge, struct syna_tcm, panel_bridge); if (bridge->encoder && bridge->encoder->crtc) { const struct drm_crtc_state *crtc_state = bridge->encoder->crtc->state; if (drm_atomic_crtc_effectively_active(crtc_state)) return; } pr_debug("%s\n", __func__); syna_set_bus_ref(tcm, SYNA_BUS_REF_SCREEN_ON, false); } static void syna_panel_bridge_mode_set(struct drm_bridge *bridge, const struct drm_display_mode *mode, const struct drm_display_mode *adjusted_mode) { struct syna_tcm *tcm = container_of(bridge, struct syna_tcm, panel_bridge); pr_debug("%s\n", __func__); if (!tcm->connector || !tcm->connector->state) { pr_info("%s: Get bridge connector.\n", __func__); tcm->connector = syna_get_bridge_connector(bridge); } tcm->is_panel_lp_mode = syna_bridge_is_lp_mode(tcm->connector); if (tcm->is_panel_lp_mode) syna_set_bus_ref(tcm, SYNA_BUS_REF_SCREEN_ON, false); else syna_set_bus_ref(tcm, SYNA_BUS_REF_SCREEN_ON, true); } static const struct drm_bridge_funcs panel_bridge_funcs = { .enable = syna_panel_bridge_enable, .disable = syna_panel_bridge_disable, .mode_set = syna_panel_bridge_mode_set, }; static int syna_register_panel_bridge(struct syna_tcm *tcm) { #ifdef CONFIG_OF tcm->panel_bridge.of_node = tcm->pdev->dev.parent->of_node; #endif tcm->panel_bridge.funcs = &panel_bridge_funcs; drm_bridge_add(&tcm->panel_bridge); return 0; } static void syna_unregister_panel_bridge(struct drm_bridge *bridge) { struct drm_bridge *node; drm_bridge_remove(bridge); if (!bridge->dev) /* not attached */ return; drm_modeset_lock(&bridge->dev->mode_config.connection_mutex, NULL); list_for_each_entry(node, &bridge->encoder->bridge_chain, chain_node) if (node == bridge) { if (bridge->funcs->detach) bridge->funcs->detach(bridge); list_del(&bridge->chain_node); break; } drm_modeset_unlock(&bridge->dev->mode_config.connection_mutex); bridge->dev = NULL; } #endif /** * syna_dev_disconnect() * * This function will power off the connected device. * Then, all the allocated resource will be released. * * @param * [ in] tcm: the driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_disconnect(struct syna_tcm *tcm) { struct syna_hw_interface *hw_if = tcm->hw_if; if (tcm->is_connected == false) { LOGI("%s already disconnected\n", PLATFORM_DRIVER_NAME); return 0; } #ifdef STARTUP_REFLASH cancel_delayed_work_sync(&tcm->reflash_work); flush_workqueue(tcm->reflash_workqueue); destroy_workqueue(tcm->reflash_workqueue); #endif /* free interrupt line */ if (hw_if->bdata_attn.irq_id) syna_dev_release_irq(tcm); /* unregister input device */ syna_dev_release_input_device(tcm); tcm->input_dev_params.max_x = 0; tcm->input_dev_params.max_y = 0; tcm->input_dev_params.max_objects = 0; /* power off */ if (hw_if->ops_power_on) hw_if->ops_power_on(hw_if, false); tcm->pwr_state = PWR_OFF; tcm->is_connected = false; LOGI("%s device disconnected\n", PLATFORM_DRIVER_NAME); return 0; } /** * syna_dev_connect() * * This function will power on and identify the connected device. * At the end of function, the ISR will be registered as well. * * @param * [ in] tcm: the driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_connect(struct syna_tcm *tcm) { int retval; struct syna_hw_interface *hw_if = tcm->hw_if; struct tcm_dev *tcm_dev = tcm->tcm_dev; if (!tcm_dev) { LOGE("Invalid tcm_dev\n"); return -EINVAL; } if (tcm->is_connected) { LOGI("%s already connected\n", PLATFORM_DRIVER_NAME); return 0; } /* power on the connected device */ if (hw_if->ops_power_on) { retval = hw_if->ops_power_on(hw_if, true); if (retval < 0) return -ENODEV; } /* perform a hardware reset */ if (hw_if->ops_hw_reset) hw_if->ops_hw_reset(hw_if); /* detect which modes of touch controller is running * * the signal of the Touch IC ready is called as "identify" * report and generated by firmware */ retval = syna_tcm_detect_device(tcm->tcm_dev); if (retval < 0) { LOGE("Fail to detect the device\n"); goto err_detect_dev; } switch (retval) { case MODE_APPLICATION_FIRMWARE: retval = syna_dev_set_up_app_fw(tcm); if (retval < 0) { #ifdef FORCE_CONNECTION LOGW("App firmware is not available yet\n"); LOGW("However, connect and skip initialization\n"); goto end; #else LOGE("Fail to set up application firmware\n"); goto err_setup_app_fw; #endif } /* allocate and register to input device subsystem */ retval = syna_dev_set_up_input_device(tcm); if (retval < 0) { LOGE("Fail to set up input device\n"); goto err_setup_input_dev; } break; default: LOGN("Application firmware not running, current mode: %02x\n", retval); break; } /* register the interrupt handler */ retval = syna_dev_request_irq(tcm); if (retval < 0) { LOGE("Fail to request the interrupt line\n"); goto err_request_irq; } /* for the reference, * create a delayed work to perform fw update during the startup time */ #ifdef STARTUP_REFLASH tcm->reflash_workqueue = create_singlethread_workqueue("syna_reflash"); INIT_DELAYED_WORK(&tcm->reflash_work, syna_dev_reflash_startup_work); queue_delayed_work(tcm->reflash_workqueue, &tcm->reflash_work, msecs_to_jiffies(STARTUP_REFLASH_DELAY_TIME_MS)); #endif #ifdef FORCE_CONNECTION end: #endif tcm->pwr_state = PWR_ON; tcm->is_connected = true; tcm->bus_refmask = SYNA_BUS_REF_SCREEN_ON; LOGI("TCM packrat: %d\n", tcm->tcm_dev->packrat_number); LOGI("Configuration: name(%s), lpwg(%s), hw_reset(%s), irq_ctrl(%s)\n", PLATFORM_DRIVER_NAME, (tcm->lpwg_enabled) ? "yes" : "no", (hw_if->ops_hw_reset) ? "yes" : "no", (hw_if->ops_enable_irq) ? "yes" : "no"); return 0; err_request_irq: /* unregister input device */ syna_dev_release_input_device(tcm); err_setup_input_dev: #ifdef FORCE_CONNECTION #else err_setup_app_fw: #endif err_detect_dev: if (hw_if->ops_power_on) hw_if->ops_power_on(hw_if, false); return retval; } #ifdef USE_DRM_PANEL_NOTIFIER static struct drm_panel *syna_dev_get_panel(struct device_node *np) { int i; int count; struct device_node *node; struct drm_panel *panel; count = of_count_phandle_with_args(np, "panel", NULL); if (count <= 0) return NULL; for (i = 0; i < count; i++) { node = of_parse_phandle(np, "panel", i); panel = of_drm_find_panel(node); of_node_put(node); if (!IS_ERR(panel)) { LOGI("Find available panel\n"); return panel; } } return NULL; } #endif /** * syna_dev_probe() * * Install the TouchComm device driver * * @param * [ in] pdev: an instance of platform device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_probe(struct platform_device *pdev) { int retval; struct syna_tcm *tcm = NULL; struct tcm_dev *tcm_dev = NULL; struct syna_hw_interface *hw_if = NULL; #if defined(USE_DRM_PANEL_NOTIFIER) struct device *dev; #endif hw_if = pdev->dev.platform_data; if (!hw_if) { LOGE("Fail to find hardware configuration\n"); return -EINVAL; } tcm = syna_pal_mem_alloc(1, sizeof(struct syna_tcm)); if (!tcm) { LOGE("Fail to create the instance of syna_tcm\n"); return -ENOMEM; } tcm->pinctrl = devm_pinctrl_get(pdev->dev.parent); if (IS_ERR_OR_NULL(tcm->pinctrl)) { LOGE("Could not get pinctrl!\n"); } else { syna_pinctrl_configure(tcm, true); } /* allocate the TouchCom device handle * recommend to set polling mode here because isr is not registered yet */ retval = syna_tcm_allocate_device(&tcm_dev, hw_if, RESP_IN_POLLING); if ((retval < 0) || (!tcm_dev)) { LOGE("Fail to allocate TouchCom device handle\n"); goto err_allocate_cdev; } tcm->tcm_dev = tcm_dev; tcm->pdev = pdev; tcm->hw_if = hw_if; syna_tcm_buf_init(&tcm->event_data); syna_pal_mutex_alloc(&tcm->tp_event_mutex); #ifdef ENABLE_WAKEUP_GESTURE tcm->lpwg_enabled = true; #else tcm->lpwg_enabled = false; #endif tcm->irq_wake = false; tcm->is_connected = false; tcm->pwr_state = PWR_OFF; tcm->dev_connect = syna_dev_connect; tcm->dev_disconnect = syna_dev_disconnect; tcm->dev_set_up_app_fw = syna_dev_set_up_app_fw; tcm->dev_set_normal_sensing = syna_dev_enter_normal_sensing; tcm->dev_set_lowpwr_sensing = syna_dev_enter_lowpwr_sensing; platform_set_drvdata(pdev, tcm); device_init_wakeup(&pdev->dev, 1); tcm->event_wq = alloc_workqueue("syna_wq", WQ_UNBOUND | WQ_HIGHPRI | WQ_CPU_INTENSIVE, 1); if (!tcm->event_wq) { LOGE("Cannot create work thread\n"); retval = -ENOMEM; goto err_alloc_workqueue; } INIT_WORK(&tcm->suspend_work, syna_suspend_work); INIT_WORK(&tcm->resume_work, syna_resume_work); init_completion(&tcm->bus_resumed); complete_all(&tcm->bus_resumed); #if defined(TCM_CONNECT_IN_PROBE) /* connect to target device */ retval = tcm->dev_connect(tcm); if (retval < 0) { LOGE("Fail to connect to the device\n"); syna_pal_mutex_free(&tcm->tp_event_mutex); goto err_connect; } #endif #ifdef HAS_SYSFS_INTERFACE /* create the device file and register to char device classes */ retval = syna_cdev_create_sysfs(tcm, pdev); if (retval < 0) { LOGE("Fail to create the device sysfs\n"); syna_pal_mutex_free(&tcm->tp_event_mutex); goto err_create_cdev; } #endif #if defined(USE_DRM_BRIDGE) retval = syna_register_panel_bridge(tcm); #elif defined(ENABLE_DISP_NOTIFIER) #if defined(USE_DRM_PANEL_NOTIFIER) dev = syna_request_managed_device(); active_panel = syna_dev_get_panel(dev->of_node); if (active_panel) { tcm->fb_notifier.notifier_call = syna_dev_fb_notifier_cb; retval = drm_panel_notifier_register(active_panel, &tcm->fb_notifier); if (retval < 0) { LOGE("Fail to register FB notifier client\n"); goto err_create_cdev; } } else { LOGE("No available drm panel\n"); } #else tcm->fb_notifier.notifier_call = syna_dev_fb_notifier_cb; retval = fb_register_client(&tcm->fb_notifier); if (retval < 0) { LOGE("Fail to register FB notifier client\n"); goto err_create_cdev; } #endif #endif LOGI("%s TouchComm driver v%d.%s installed\n", PLATFORM_DRIVER_NAME, SYNAPTICS_TCM_DRIVER_VERSION, SYNAPTICS_TCM_DRIVER_SUBVER); return 0; #ifdef HAS_SYSFS_INTERFACE err_create_cdev: syna_tcm_remove_device(tcm->tcm_dev); #endif #if defined(TCM_CONNECT_IN_PROBE) tcm->dev_disconnect(tcm); err_connect: #endif if (tcm->event_wq) destroy_workqueue(tcm->event_wq); err_alloc_workqueue: syna_tcm_buf_release(&tcm->event_data); syna_pal_mutex_free(&tcm->tp_event_mutex); err_allocate_cdev: syna_pal_mem_free((void *)tcm); return retval; } /** * syna_dev_remove() * * Release all allocated resources and remove the TouchCom device handle * * @param * [ in] pdev: an instance of platform device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_remove(struct platform_device *pdev) { struct syna_tcm *tcm = platform_get_drvdata(pdev); if (!tcm) { LOGW("Invalid handle to remove\n"); return 0; } #if defined(USE_DRM_BRIDGE) syna_unregister_panel_bridge(&tcm->panel_bridge); #elif defined(ENABLE_DISP_NOTIFIER) #if defined(USE_DRM_PANEL_NOTIFIER) if (active_panel) drm_panel_notifier_unregister(active_panel, &tcm->fb_notifier); #else fb_unregister_client(&tcm->fb_notifier); #endif #endif #ifdef HAS_SYSFS_INTERFACE /* remove the cdev and sysfs nodes */ syna_cdev_remove_sysfs(tcm); #endif /* check the connection statusm, and do disconnection */ if (tcm->dev_disconnect(tcm) < 0) LOGE("Fail to do device disconnection\n"); syna_tcm_buf_release(&tcm->event_data); syna_pal_mutex_free(&tcm->tp_event_mutex); /* remove the allocated tcm device */ syna_tcm_remove_device(tcm->tcm_dev); /* release the device context */ syna_pal_mem_free((void *)tcm); return 0; } /** * syna_dev_shutdown() * * Call syna_dev_remove() to release all resources * * @param * [in] pdev: an instance of platform device * * @return * none. */ static void syna_dev_shutdown(struct platform_device *pdev) { syna_dev_remove(pdev); } /** * Declare a TouchComm platform device */ #ifdef CONFIG_PM #if defined(USE_DRM_BRIDGE) static int syna_pm_suspend(struct device *dev) { struct syna_tcm *tcm = dev_get_drvdata(dev); if (tcm->bus_refmask) LOGW("bus_refmask 0x%X\n", tcm->bus_refmask); if (tcm->pwr_state == PWR_ON) { LOGW("can't suspend because touch bus is in use!\n"); if (tcm->bus_refmask == SYNA_BUS_REF_BUGREPORT && ktime_ms_delta(ktime_get(), tcm->bugreport_ktime_start) > 30 * MSEC_PER_SEC) { syna_set_bus_ref(tcm, SYNA_BUS_REF_BUGREPORT, false); pm_relax(&tcm->pdev->dev); tcm->bugreport_ktime_start = 0; LOGE("Force release SYNA_BUS_REF_BUGREPORT reference bit."); } return -EBUSY; } return 0; } static int syna_pm_resume(struct device *dev) { return 0; } #endif static const struct dev_pm_ops syna_dev_pm_ops = { #if defined(USE_DRM_BRIDGE) .suspend = syna_pm_suspend, .resume = syna_pm_resume, #elif !defined(ENABLE_DISP_NOTIFIER) .suspend = syna_dev_suspend, .resume = syna_dev_resume, #endif }; #endif static struct platform_driver syna_dev_driver = { .driver = { .name = PLATFORM_DRIVER_NAME, .owner = THIS_MODULE, #ifdef CONFIG_PM .pm = &syna_dev_pm_ops, #endif }, .probe = syna_dev_probe, .remove = syna_dev_remove, .shutdown = syna_dev_shutdown, }; /** * syna_dev_module_init() * * The entry function of the reference driver, which initialize the * lower-level bus and register a platform driver. * * @param * void. * * @return * 0 if the driver registered and bound to a device, * else returns a negative error code and with the driver not registered. */ static int __init syna_dev_module_init(void) { int retval; retval = syna_hw_interface_init(); if (retval < 0) return retval; return platform_driver_register(&syna_dev_driver); } /** * syna_dev_module_exit() * * Function is called when un-installing the driver. * Remove the registered platform driver and the associated bus driver. * * @param * void. * * @return * none. */ static void __exit syna_dev_module_exit(void) { platform_driver_unregister(&syna_dev_driver); syna_hw_interface_exit(); } module_init(syna_dev_module_init); module_exit(syna_dev_module_exit); MODULE_AUTHOR("Synaptics, Inc."); MODULE_DESCRIPTION("Synaptics TCM Touch Driver"); MODULE_LICENSE("GPL v2");