| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * pt_mt_common.c |
| * Parade TrueTouch(TM) Standard Product Multi-Touch Reports Module. |
| * For use with Parade touchscreen controllers. |
| * Supported parts include: |
| * TMA5XX |
| * TMA448 |
| * TMA445A |
| * TT21XXX |
| * TT31XXX |
| * TT4XXXX |
| * TT7XXX |
| * TC3XXX |
| * |
| * Copyright (C) 2015-2020 Parade Technologies |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * version 2, and only version 2, as published by the |
| * Free Software Foundation. |
| * |
| * 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. |
| * |
| * Contact Parade Technologies at www.paradetech.com <ttdrivers@paradetech.com> |
| */ |
| |
| #include "pt_regs.h" |
| #include "nanohub_exports.h" |
| #include <linux/input-event-codes.h> |
| #include <linux/input/mt.h> |
| |
| #define MT_PARAM_SIGNAL(md, sig_ost) PARAM_SIGNAL(md->pdata->frmwrk, sig_ost) |
| #define MT_PARAM_MIN(md, sig_ost) PARAM_MIN(md->pdata->frmwrk, sig_ost) |
| #define MT_PARAM_MAX(md, sig_ost) PARAM_MAX(md->pdata->frmwrk, sig_ost) |
| #define MT_PARAM_FUZZ(md, sig_ost) PARAM_FUZZ(md->pdata->frmwrk, sig_ost) |
| #define MT_PARAM_FLAT(md, sig_ost) PARAM_FLAT(md->pdata->frmwrk, sig_ost) |
| |
| extern struct pt_input_event touch_event_list_head; |
| extern void queue_input_event(uint16_t type, uint16_t code, int32_t value, ktime_t timestamp); |
| struct mcu_input_message { |
| uint8_t type; |
| uint32_t code; |
| int32_t value; |
| } __packed; |
| |
| static inline void assign_input_message(uint8_t type, uint32_t code, int32_t value, |
| struct mcu_input_message *input) |
| { |
| if (unlikely(input == NULL)) |
| return; |
| |
| memcpy(&input->type, &type, sizeof(input->type)); |
| memcpy(&input->code, &code, sizeof(input->code)); |
| memcpy(&input->value, &value, sizeof(input->value)); |
| } |
| |
| static int pt_send_mcu_message(unsigned int type, unsigned int code, int value) |
| { |
| ssize_t bytes; |
| struct mcu_input_message input; |
| |
| assign_input_message(type, code, value, &input); |
| |
| bytes = nanohub_send_message(NANOHUB_TOUCH_CHANNEL_ID, (const char *)&input, sizeof(input)); |
| if (bytes != sizeof(input)) { |
| pr_err("MCU Bytes sent expected = %zd, actual = %zd\n", sizeof(input), bytes); |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| static inline void pt_mcu_report_key(unsigned int code, int value) |
| { |
| pt_send_mcu_message(EV_KEY, code, value); |
| } |
| |
| static inline void pt_mcu_report_abs(unsigned int code, int value) |
| { |
| pt_send_mcu_message(EV_ABS, code, value); |
| } |
| |
| static inline void pt_mcu_sync(void) |
| { |
| pt_send_mcu_message(EV_SYN, SYN_REPORT, 0); |
| } |
| |
| static void pt_mcu_send_key(uint32_t keycode) |
| { |
| pt_mcu_report_key(keycode, 1); |
| pt_mcu_sync(); |
| pt_mcu_report_key(keycode, 0); |
| pt_mcu_sync(); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_mt_lift_all |
| * |
| * SUMMARY: Reports touch liftoff action |
| * |
| * PARAMETERS: |
| * *md - pointer to touch data structure |
| ******************************************************************************/ |
| static void pt_mt_lift_all(struct pt_mt_data *md) |
| { |
| int max = md->si->tch_abs[PT_TCH_T].max; |
| |
| if (md->num_prv_rec != 0) { |
| if (md->mt_function.report_slot_liftoff) |
| md->mt_function.report_slot_liftoff(md, max); |
| if (md->touch_state == QUEUEING_TOUCH_EVENTS) |
| queue_input_event(EV_SYN, SYN_REPORT, 0, 0); |
| else |
| input_sync(md->input); |
| md->num_prv_rec = 0; |
| } |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_touch_hdr |
| * |
| * SUMMARY: Get the header of touch report |
| * |
| * PARAMETERS: |
| * *md - pointer to touch data structure |
| * *touch - pointer to pt_touch structure |
| * *xy_mode - pointer to touch mode data |
| ******************************************************************************/ |
| static void pt_get_touch_hdr(struct pt_mt_data *md, |
| struct pt_touch *touch, u8 *xy_mode) |
| { |
| struct device *dev = md->dev; |
| struct pt_sysinfo *si = md->si; |
| enum pt_tch_hdr hdr; |
| |
| for (hdr = PT_TCH_TIME; hdr < PT_TCH_NUM_HDR; hdr++) { |
| if (!si->tch_hdr[hdr].report) |
| continue; |
| pt_get_touch_field(dev, &touch->hdr[hdr], |
| si->tch_hdr[hdr].size, |
| si->tch_hdr[hdr].max, |
| xy_mode + si->tch_hdr[hdr].ofs, |
| si->tch_hdr[hdr].bofs); |
| pt_debug(dev, DL_DEBUG, "%s: get %s=%04X(%d)\n", |
| __func__, pt_tch_hdr_string[hdr], |
| touch->hdr[hdr], touch->hdr[hdr]); |
| } |
| |
| pt_debug(dev, DL_DEBUG, "%s: time=%X tch_num=%d lo=%d noise=%d counter=%d\n", __func__, |
| touch->hdr[PT_TCH_TIME], touch->hdr[PT_TCH_NUM], touch->hdr[PT_TCH_LO], |
| touch->hdr[PT_TCH_NOISE], touch->hdr[PT_TCH_COUNTER]); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_touch_record |
| * |
| * SUMMARY: Gets axis of touch report |
| * |
| * PARAMETERS: |
| * *md - pointer to touch data structure |
| * *touch - pointer to pt_touch structure |
| * *xy_data - pointer to touch data |
| ******************************************************************************/ |
| static void pt_get_touch_record(struct pt_mt_data *md, |
| struct pt_touch *touch, u8 *xy_data) |
| { |
| struct device *dev = md->dev; |
| struct pt_sysinfo *si = md->si; |
| enum pt_tch_abs abs; |
| |
| for (abs = PT_TCH_X; abs < PT_TCH_NUM_ABS; abs++) { |
| if (!si->tch_abs[abs].report) |
| continue; |
| pt_get_touch_field(dev, &touch->abs[abs], |
| si->tch_abs[abs].size, |
| si->tch_abs[abs].max, |
| xy_data + si->tch_abs[abs].ofs, |
| si->tch_abs[abs].bofs); |
| pt_debug(dev, DL_DEBUG, "%s: get %s=%04X(%d)\n", |
| __func__, pt_tch_abs_string[abs], |
| touch->abs[abs], touch->abs[abs]); |
| } |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_mt_process_touch |
| * |
| * SUMMARY: Process touch includes oritation,axis invert and |
| * convert MAJOR/MINOR from mm to resolution |
| * |
| * PARAMETERS: |
| * *md - pointer to touch data structure |
| * *touch - pointer to pt_touch structure |
| ******************************************************************************/ |
| static void pt_mt_process_touch(struct pt_mt_data *md, |
| struct pt_touch *touch) |
| { |
| struct device *dev = md->dev; |
| struct pt_sysinfo *si = md->si; |
| int tmp; |
| bool flipped; |
| |
| /* Orientation is signed */ |
| touch->abs[PT_TCH_OR] = (int8_t)touch->abs[PT_TCH_OR]; |
| |
| if (md->pdata->flags & PT_MT_FLAG_FLIP) { |
| swap(touch->abs[PT_TCH_X], touch->abs[PT_TCH_Y]); |
| if (touch->abs[PT_TCH_OR] > 0) |
| touch->abs[PT_TCH_OR] = |
| md->or_max - touch->abs[PT_TCH_OR]; |
| else |
| touch->abs[PT_TCH_OR] = |
| md->or_min - touch->abs[PT_TCH_OR]; |
| flipped = true; |
| } else |
| flipped = false; |
| |
| /* |
| * 1 is subtracted from each touch location to make the location |
| * 0 based. e.g. If the resolution of touch panel is 1200x1600, |
| * the FW touch report must be (0~1199,0~1599). The driver |
| * should register the (min,max) value to Linux input system as |
| * (0~1199,0~1599). When the host needs to invert the |
| * coordinates, the driver would incorrectly use the resolution |
| * to subtract the reported point directly, such as |
| * 1200-(0~1199). The input system will lose the 0 point report |
| * and the 1200 point will be ignored. |
| */ |
| if (md->pdata->flags & PT_MT_FLAG_INV_X) { |
| if (flipped) |
| touch->abs[PT_TCH_X] = si->sensing_conf_data.res_y - |
| touch->abs[PT_TCH_X] - 1; |
| else |
| touch->abs[PT_TCH_X] = si->sensing_conf_data.res_x - |
| touch->abs[PT_TCH_X] - 1; |
| touch->abs[PT_TCH_OR] *= -1; |
| } |
| if (md->pdata->flags & PT_MT_FLAG_INV_Y) { |
| if (flipped) |
| touch->abs[PT_TCH_Y] = si->sensing_conf_data.res_x - |
| touch->abs[PT_TCH_Y] - 1; |
| else |
| touch->abs[PT_TCH_Y] = si->sensing_conf_data.res_y - |
| touch->abs[PT_TCH_Y] - 1; |
| touch->abs[PT_TCH_OR] *= -1; |
| } |
| |
| /* Convert MAJOR/MINOR from mm to resolution */ |
| tmp = touch->abs[PT_TCH_MAJ] * 100 * si->sensing_conf_data.res_x; |
| touch->abs[PT_TCH_MAJ] = tmp / si->sensing_conf_data.len_x; |
| tmp = touch->abs[PT_TCH_MIN] * 100 * si->sensing_conf_data.res_x; |
| touch->abs[PT_TCH_MIN] = tmp / si->sensing_conf_data.len_x; |
| |
| |
| pt_debug(dev, DL_DEBUG, |
| "%s: flip=%s inv-x=%s inv-y=%s x=%04X(%d) y=%04X(%d)\n", |
| __func__, flipped ? "true" : "false", |
| md->pdata->flags & PT_MT_FLAG_INV_X ? "true" : "false", |
| md->pdata->flags & PT_MT_FLAG_INV_Y ? "true" : "false", |
| touch->abs[PT_TCH_X], touch->abs[PT_TCH_X], |
| touch->abs[PT_TCH_Y], touch->abs[PT_TCH_Y]); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_report_event |
| * |
| * SUMMARY: Reports touch event |
| * |
| * PARAMETERS: |
| * *md - pointer to touch data structure |
| * event - type of touch event |
| * value - value of report event |
| ******************************************************************************/ |
| static void pt_report_event(struct pt_mt_data *md, int event, |
| int value) |
| { |
| int sig = MT_PARAM_SIGNAL(md, event); |
| |
| if (sig != PT_IGNORE_VALUE) { |
| if (md->touch_state == QUEUEING_TOUCH_EVENTS) |
| queue_input_event(EV_ABS, sig, value, 0); |
| else |
| input_report_abs(md->input, sig, value); |
| } |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_mt_touches |
| * |
| * SUMMARY: Parse and report touch event |
| * |
| * PARAMETERS: |
| * *md - pointer to touch data structure |
| * *tch - pointer to touch structure |
| * num_cur_tch - number of current touch |
| ******************************************************************************/ |
| static void pt_get_mt_touches(struct pt_mt_data *md, |
| struct pt_touch *tch, int num_cur_tch) |
| { |
| struct device *dev = md->dev; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_sysinfo *si = md->si; |
| int sig; |
| int i, j, t = 0; |
| DECLARE_BITMAP(ids, PT_TOUCH_ID_MAX); |
| int mt_sync_count = 0; |
| u8 *tch_addr; |
| |
| if (PT_TOUCH_ID_MAX < si->tch_abs[PT_TCH_T].max) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Touch ID %d is allocated less than needed %d\n", |
| __func__, PT_TOUCH_ID_MAX, |
| (int)si->tch_abs[PT_TCH_T].max); |
| return; |
| } |
| |
| bitmap_zero(ids, PT_TOUCH_ID_MAX); |
| memset(tch->abs, 0, sizeof(tch->abs)); |
| |
| for (i = 0; i < num_cur_tch; i++) { |
| tch_addr = si->xy_data + (i * si->desc.tch_record_size); |
| pt_get_touch_record(md, tch, tch_addr); |
| |
| /* Discard proximity event */ |
| if (tch->abs[PT_TCH_O] == PT_OBJ_PROXIMITY) { |
| pt_debug(dev, DL_INFO, |
| "%s: Discarding proximity event\n", |
| __func__); |
| continue; |
| } |
| |
| /* Validate track_id */ |
| t = tch->abs[PT_TCH_T]; |
| if (t < md->t_min || t > md->t_max) { |
| pt_debug(dev, DL_DEBUG, "%s: tch=%d -> bad trk_id=%d max_id=%d\n", __func__, |
| i, t, md->t_max); |
| if (md->mt_function.input_sync) |
| md->mt_function.input_sync(md->input); |
| mt_sync_count++; |
| continue; |
| } |
| |
| /* Lift-off */ |
| if (tch->abs[PT_TCH_E] == PT_EV_LIFTOFF) { |
| pt_debug(dev, DL_DEBUG, "%s: t=%d e=%d lift-off\n", __func__, t, |
| tch->abs[PT_TCH_E]); |
| goto pt_get_mt_touches_pr_tch; |
| } |
| |
| /* Process touch */ |
| pt_mt_process_touch(md, tch); |
| |
| /* use 0 based track id's */ |
| t -= md->t_min; |
| |
| sig = MT_PARAM_SIGNAL(md, PT_ABS_ID_OST); |
| if (sig != PT_IGNORE_VALUE) { |
| if (md->mt_function.input_report) { |
| /* Resuming due to active touch*/ |
| if (md->touch_state == WAKING_BY_ACTIVE_TOUCH) { |
| if (cd->drm_panel_state == DRM_PANEL_OFF) { |
| pt_debug(dev, DL_INFO, |
| "%s: start to queue touch events\n", |
| __func__); |
| md->touch_state = QUEUEING_TOUCH_EVENTS; |
| queue_input_event(EV_TIMESTAMP, 0, 0, |
| cd->timestamp); |
| } |
| input_report_key(md->input, KEY_WAKEUP, 1); |
| input_sync(md->input); |
| input_report_key(md->input, KEY_WAKEUP, 0); |
| input_sync(md->input); |
| } |
| md->mt_function.input_report(md->input, sig, |
| t, tch->abs[PT_TCH_O]); |
| } |
| __set_bit(t, ids); |
| } |
| |
| pt_report_event(md, PT_ABS_D_OST, 0); |
| |
| /* all devices: position and pressure fields */ |
| for (j = 0; j <= PT_ABS_W_OST; j++) { |
| if (!si->tch_abs[j].report) |
| continue; |
| pt_report_event(md, PT_ABS_X_OST + j, |
| tch->abs[PT_TCH_X + j]); |
| } |
| |
| /* Get the extended touch fields */ |
| for (j = 0; j < PT_NUM_EXT_TCH_FIELDS; j++) { |
| if (!si->tch_abs[PT_ABS_MAJ_OST + j].report) |
| continue; |
| pt_report_event(md, PT_ABS_MAJ_OST + j, |
| tch->abs[PT_TCH_MAJ + j]); |
| } |
| if (md->mt_function.input_sync) |
| md->mt_function.input_sync(md->input); |
| mt_sync_count++; |
| |
| pt_get_mt_touches_pr_tch: |
| pt_debug(dev, DL_DEBUG, |
| "%s: t=%d x=%d y=%d z=%d M=%d m=%d o=%d e=%d obj=%d tip=%d\n", __func__, t, |
| tch->abs[PT_TCH_X], tch->abs[PT_TCH_Y], tch->abs[PT_TCH_P], |
| tch->abs[PT_TCH_MAJ], tch->abs[PT_TCH_MIN], tch->abs[PT_TCH_OR], |
| tch->abs[PT_TCH_E], tch->abs[PT_TCH_O], tch->abs[PT_TCH_TIP]); |
| } |
| |
| if (md->mt_function.final_sync) |
| md->mt_function.final_sync(md->input, |
| si->tch_abs[PT_TCH_T].max, mt_sync_count, ids); |
| |
| md->num_prv_rec = num_cur_tch; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_xy_worker |
| * |
| * SUMMARY: Read xy_data for all current touches |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *md - pointer to touch data structure |
| ******************************************************************************/ |
| static int pt_xy_worker(struct pt_mt_data *md) |
| { |
| struct device *dev = md->dev; |
| struct pt_sysinfo *si = md->si; |
| int max_tch = si->sensing_conf_data.max_tch; |
| struct pt_touch tch; |
| u8 num_cur_tch; |
| int rc = 0; |
| |
| pt_get_touch_hdr(md, &tch, si->xy_mode + 3); |
| |
| num_cur_tch = tch.hdr[PT_TCH_NUM]; |
| if (num_cur_tch > max_tch) { |
| pt_debug(dev, DL_ERROR, "%s: Num touch err detected (n=%d)\n", |
| __func__, num_cur_tch); |
| num_cur_tch = max_tch; |
| } |
| |
| if (tch.hdr[PT_TCH_LO]) { |
| pt_debug(dev, DL_INFO, "%s: Large area detected\n", __func__); |
| if (md->pdata->flags & PT_MT_FLAG_NO_TOUCH_ON_LO) |
| num_cur_tch = 0; |
| input_report_key(md->input, KEY_SLEEP, 1); |
| input_sync(md->input); |
| input_report_key(md->input, KEY_SLEEP, 0); |
| input_sync(md->input); |
| pt_mcu_send_key(KEY_SLEEP); |
| pt_mt_lift_all(md); |
| return 0; |
| } |
| |
| if (num_cur_tch == 0 && md->num_prv_rec == 0) |
| goto pt_xy_worker_exit; |
| |
| /* extract xy_data for all currently reported touches */ |
| pt_debug(dev, DL_DEBUG, "%s: extract data num_cur_tch=%d\n", |
| __func__, num_cur_tch); |
| |
| if (num_cur_tch) |
| pt_get_mt_touches(md, &tch, num_cur_tch); |
| else |
| pt_mt_lift_all(md); |
| |
| rc = 0; |
| |
| pt_xy_worker_exit: |
| return rc; |
| } |
| |
| static void pt_gesture_event_handle_orientation(struct pt_mt_data *md, uint32_t *input_x, |
| uint32_t *input_y) |
| { |
| struct pt_sysinfo *si = md->si; |
| bool flipped; |
| |
| if (md->pdata->flags & PT_MT_FLAG_FLIP) { |
| swap(*input_x, *input_y); |
| flipped = true; |
| } else { |
| flipped = false; |
| } |
| |
| if (md->pdata->flags & PT_MT_FLAG_INV_X) { |
| if (flipped) |
| *input_x = si->sensing_conf_data.res_y - *input_x - 1; |
| else |
| *input_x = si->sensing_conf_data.res_x - *input_x - 1; |
| } |
| |
| if (md->pdata->flags & PT_MT_FLAG_INV_Y) { |
| if (flipped) |
| *input_y = si->sensing_conf_data.res_x - *input_y - 1; |
| else |
| *input_y = si->sensing_conf_data.res_y - *input_y - 1; |
| } |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_mt_send_dummy_event |
| * |
| * SUMMARY: Send dummy/key event to wakeup upper layer of system |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * *md - pointer to touch data structure |
| ******************************************************************************/ |
| static void pt_mt_send_dummy_event(struct pt_core_data *cd, |
| struct pt_mt_data *md) |
| { |
| #ifndef EASYWAKE_TSG6 |
| /* TSG5 EasyWake */ |
| unsigned long ids = 0; |
| |
| /* for easy wakeup */ |
| if (md->mt_function.input_report) |
| md->mt_function.input_report(md->input, ABS_MT_TRACKING_ID, |
| 0, PT_OBJ_STANDARD_FINGER); |
| if (md->mt_function.input_sync) |
| md->mt_function.input_sync(md->input); |
| if (md->mt_function.final_sync) |
| md->mt_function.final_sync(md->input, 0, 1, &ids); |
| if (md->mt_function.report_slot_liftoff) |
| md->mt_function.report_slot_liftoff(md, 1); |
| if (md->mt_function.final_sync) |
| md->mt_function.final_sync(md->input, 1, 1, &ids); |
| #else |
| /* TSG6 FW1.3 and above only. TSG6 FW1.0 - 1.2 does not */ |
| /* support EasyWake, and this function will not be called */ |
| u8 key_value = 0; |
| |
| switch (cd->gesture_id) { |
| case GESTURE_SINGLE_TAP: { |
| uint32_t input_x = |
| (uint32_t)(cd->gesture_data[1] << 8) + (uint32_t)(cd->gesture_data[0]); |
| uint32_t input_y = |
| (uint32_t)(cd->gesture_data[3] << 8) + (uint32_t)(cd->gesture_data[2]); |
| |
| pt_gesture_event_handle_orientation(md, &input_x, &input_y); |
| pt_debug(md->dev, DL_INFO, |
| "%s:Gesture : Tap to Wake, coordinates (%d,%d) instead of keycode.\n", |
| __func__, input_x, input_y); |
| /* Send finger down */ |
| input_mt_slot(md->input, 0); |
| input_mt_report_slot_state(md->input, MT_TOOL_FINGER, true); |
| input_report_abs(md->input, ABS_MT_POSITION_X, input_x); |
| input_report_abs(md->input, ABS_MT_POSITION_Y, input_y); |
| /* Placeholder pressure value, without this the |
| * touch is interpreted as a hover. |
| */ |
| input_report_abs(md->input, ABS_MT_PRESSURE, 0x300); |
| input_mt_sync_frame(md->input); |
| input_sync(md->input); |
| |
| /* Send finger up */ |
| input_mt_slot(md->input, 0); |
| input_report_abs(md->input, ABS_MT_TOUCH_MAJOR, 0); |
| input_mt_report_slot_state(md->input, MT_TOOL_FINGER, false); |
| input_mt_sync_frame(md->input); |
| input_sync(md->input); |
| } break; |
| case GESTURE_SINGLE_SLIDE_DE_TX: |
| key_value = KEY_UP; |
| break; |
| case GESTURE_SINGLE_SLIDE_IN_TX: |
| key_value = KEY_DOWN; |
| break; |
| case GESTURE_SINGLE_SLIDE_DE_RX: |
| key_value = KEY_RIGHT; |
| break; |
| case GESTURE_SINGLE_SLIDE_IN_RX: |
| key_value = KEY_LEFT; |
| break; |
| case GESTURE_PALM: |
| pt_mcu_send_key(KEY_SLEEP); |
| break; |
| default: |
| break; |
| } |
| |
| if (key_value > 0) { |
| input_report_key(md->input, key_value, 1); |
| input_sync(md->input); |
| input_report_key(md->input, key_value, 0); |
| input_sync(md->input); |
| } |
| |
| /* |
| * Caution - this debug print is needed by the TTDL automated |
| * regression test suite |
| */ |
| pt_debug(md->dev, DL_INFO, "%s: report key: %d. gesture_id: %d\n", __func__, key_value, |
| cd->gesture_id); |
| #endif |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_mt_attention |
| * |
| * SUMMARY: Wrapper function for pt_xy_worker() that subscribe into the TTDL |
| * attention list. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int pt_mt_attention(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_mt_data *md = &cd->md; |
| int rc; |
| |
| if (md->si->xy_mode[2] != md->si->desc.tch_report_id) |
| return 0; |
| |
| /* core handles handshake */ |
| mutex_lock(&md->mt_lock); |
| if (md->touch_state == QUEUEING_TOUCH_EVENTS) { |
| queue_input_event(EV_TIMESTAMP, 0, 0, cd->timestamp); |
| } else { |
| input_set_timestamp(md->input, cd->timestamp); |
| } |
| rc = pt_xy_worker(md); |
| mutex_unlock(&md->mt_lock); |
| if (rc < 0) |
| pt_debug(dev, DL_ERROR, |
| "%s: xy_worker error r=%d\n", __func__, rc); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_mt_wake_attention |
| * |
| * SUMMARY: Wrapper function for pt_mt_send_dummy_event() that register to |
| * attention list. |
| * |
| * RETURN: |
| * 0 = success |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int pt_mt_wake_attention(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_mt_data *md = &cd->md; |
| |
| mutex_lock(&md->mt_lock); |
| input_set_timestamp(md->input, cd->timestamp); |
| pt_mt_send_dummy_event(cd, md); |
| mutex_unlock(&md->mt_lock); |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_startup_attention |
| * |
| * SUMMARY: Wrapper function for pt_mt_lift_all() that subcribe into the TTDL |
| * attention list. |
| * |
| * RETURN: |
| * 0 = success |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int pt_startup_attention(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_mt_data *md = &cd->md; |
| |
| mutex_lock(&md->mt_lock); |
| pt_mt_lift_all(md); |
| mutex_unlock(&md->mt_lock); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_mt_suspend_attention |
| * |
| * SUMMARY: Function for touch to enter suspend state that as following steps: |
| * 1) Lift all touch |
| * 2) Set flag with suspend state |
| * 3) Decrese pm system count |
| * |
| * RETURN: |
| * 0 = success |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int pt_mt_suspend_attention(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_mt_data *md = &cd->md; |
| |
| mutex_lock(&md->mt_lock); |
| pt_mt_lift_all(md); |
| md->is_suspended = true; |
| mutex_unlock(&md->mt_lock); |
| |
| if (!(cd->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME)) { |
| pm_runtime_put(dev); |
| } |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_mt_resume_attention |
| * |
| * SUMMARY: Function for touch to leave suspend state that as following steps: |
| * 1) Increse pm system count |
| * 2) Clear suspend state flag |
| * |
| * RETURN: |
| * 0 = success |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int pt_mt_resume_attention(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_mt_data *md = &cd->md; |
| |
| if (!(cd->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME)) { |
| pm_runtime_get(dev); |
| } |
| |
| mutex_lock(&md->mt_lock); |
| md->is_suspended = false; |
| mutex_unlock(&md->mt_lock); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_mt_open |
| * |
| * SUMMARY: Open method for input device(touch) that sets up call back |
| * functions to TTDL attention list |
| * |
| * RETURN: |
| * 0 = success |
| * |
| * PARAMETERS: |
| * *input - pointer to input_dev structure |
| ******************************************************************************/ |
| static int pt_mt_open(struct input_dev *input) |
| { |
| struct device *dev = input->dev.parent; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_mt_data *md = &cd->md; |
| |
| pm_runtime_get_sync(dev); |
| |
| mutex_lock(&md->mt_lock); |
| md->is_suspended = false; |
| mutex_unlock(&md->mt_lock); |
| |
| enable_irq_wake(cd->irq); |
| |
| pt_debug(dev, DL_INFO, "%s: setup subscriptions\n", __func__); |
| |
| /* set up touch call back */ |
| _pt_subscribe_attention(dev, PT_ATTEN_IRQ, PT_MT_NAME, |
| pt_mt_attention, PT_MODE_OPERATIONAL); |
| |
| /* set up startup call back */ |
| _pt_subscribe_attention(dev, PT_ATTEN_STARTUP, PT_MT_NAME, |
| pt_startup_attention, 0); |
| |
| /* set up wakeup call back */ |
| _pt_subscribe_attention(dev, PT_ATTEN_WAKE, PT_MT_NAME, |
| pt_mt_wake_attention, 0); |
| |
| /* set up suspend call back */ |
| _pt_subscribe_attention(dev, PT_ATTEN_SUSPEND, PT_MT_NAME, |
| pt_mt_suspend_attention, 0); |
| |
| /* set up resume call back */ |
| _pt_subscribe_attention(dev, PT_ATTEN_RESUME, PT_MT_NAME, |
| pt_mt_resume_attention, 0); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_mt_close |
| * |
| * SUMMARY: Close method for input device(touch) that clears call back |
| * functions from TTDL attention list. |
| * |
| * PARAMETERS: |
| * *input - pointer to input_dev structure |
| ******************************************************************************/ |
| static void pt_mt_close(struct input_dev *input) |
| { |
| struct device *dev = input->dev.parent; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_mt_data *md = &cd->md; |
| |
| disable_irq_wake(cd->irq); |
| |
| _pt_unsubscribe_attention(dev, PT_ATTEN_IRQ, PT_MT_NAME, |
| pt_mt_attention, PT_MODE_OPERATIONAL); |
| |
| _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, PT_MT_NAME, |
| pt_startup_attention, 0); |
| |
| _pt_unsubscribe_attention(dev, PT_ATTEN_WAKE, PT_MT_NAME, |
| pt_mt_wake_attention, 0); |
| |
| _pt_unsubscribe_attention(dev, PT_ATTEN_SUSPEND, PT_MT_NAME, |
| pt_mt_suspend_attention, 0); |
| |
| _pt_unsubscribe_attention(dev, PT_ATTEN_RESUME, PT_MT_NAME, |
| pt_mt_resume_attention, 0); |
| |
| mutex_lock(&md->mt_lock); |
| if (!md->is_suspended) { |
| pm_runtime_put(dev); |
| md->is_suspended = true; |
| } |
| mutex_unlock(&md->mt_lock); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_setup_input_device |
| * |
| * SUMMARY: Set up resolution, event signal capabilities and |
| * register input device for touch. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int pt_setup_input_device(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_mt_data *md = &cd->md; |
| int signal = PT_IGNORE_VALUE; |
| int max_x, max_y, max_p, min, max; |
| int max_x_tmp, max_y_tmp; |
| int i; |
| int rc; |
| |
| pt_debug(dev, DL_INFO, "%s: Initialize event signals\n", |
| __func__); |
| __set_bit(EV_ABS, md->input->evbit); |
| __set_bit(EV_REL, md->input->evbit); |
| __set_bit(EV_KEY, md->input->evbit); |
| #ifdef INPUT_PROP_DIRECT |
| __set_bit(INPUT_PROP_DIRECT, md->input->propbit); |
| #endif |
| |
| /* If virtualkeys enabled, don't use all screen */ |
| if (md->pdata->flags & PT_MT_FLAG_VKEYS) { |
| max_x_tmp = md->pdata->vkeys_x; |
| max_y_tmp = md->pdata->vkeys_y; |
| } else { |
| max_x_tmp = md->si->sensing_conf_data.res_x; |
| max_y_tmp = md->si->sensing_conf_data.res_y; |
| } |
| |
| /* get maximum values from the sysinfo data */ |
| if (md->pdata->flags & PT_MT_FLAG_FLIP) { |
| max_x = max_y_tmp - 1; |
| max_y = max_x_tmp - 1; |
| } else { |
| max_x = max_x_tmp - 1; |
| max_y = max_y_tmp - 1; |
| } |
| max_p = md->si->sensing_conf_data.max_z; |
| |
| /* set event signal capabilities */ |
| for (i = 0; i < NUM_SIGNALS(md->pdata->frmwrk); i++) { |
| signal = MT_PARAM_SIGNAL(md, i); |
| if (signal != PT_IGNORE_VALUE) { |
| __set_bit(signal, md->input->absbit); |
| |
| min = MT_PARAM_MIN(md, i); |
| max = MT_PARAM_MAX(md, i); |
| if (i == PT_ABS_ID_OST) { |
| /* shift track ids down to start at 0 */ |
| max = max - min; |
| min = min - min; |
| } else if (i == PT_ABS_X_OST) |
| max = max_x; |
| else if (i == PT_ABS_Y_OST) |
| max = max_y; |
| else if (i == PT_ABS_P_OST) |
| max = max_p; |
| |
| input_set_abs_params(md->input, signal, min, max, |
| MT_PARAM_FUZZ(md, i), MT_PARAM_FLAT(md, i)); |
| pt_debug(dev, DL_INFO, |
| "%s: register signal=%02X min=%d max=%d\n", |
| __func__, signal, min, max); |
| } |
| } |
| |
| md->or_min = MT_PARAM_MIN(md, PT_ABS_OR_OST); |
| md->or_max = MT_PARAM_MAX(md, PT_ABS_OR_OST); |
| |
| md->t_min = MT_PARAM_MIN(md, PT_ABS_ID_OST); |
| md->t_max = MT_PARAM_MAX(md, PT_ABS_ID_OST); |
| |
| rc = md->mt_function.input_register_device(md->input, |
| md->si->tch_abs[PT_TCH_T].max); |
| if (rc < 0) |
| pt_debug(dev, DL_ERROR, "%s: Error, failed register input device r=%d\n", |
| __func__, rc); |
| else |
| md->input_device_registered = true; |
| |
| #ifdef EASYWAKE_TSG6 |
| input_set_capability(md->input, EV_KEY, KEY_UP); |
| input_set_capability(md->input, EV_KEY, KEY_LEFT); |
| input_set_capability(md->input, EV_KEY, KEY_RIGHT); |
| input_set_capability(md->input, EV_KEY, KEY_DOWN); |
| input_set_capability(md->input, EV_KEY, KEY_WAKEUP); |
| input_set_capability(md->input, EV_KEY, KEY_SLEEP); |
| #endif |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_setup_input_attention |
| * |
| * SUMMARY: Wrapper function for pt_setup_input_device() register to TTDL |
| * attention list. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int pt_setup_input_attention(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_mt_data *md = &cd->md; |
| int rc; |
| |
| md->si = _pt_request_sysinfo(dev); |
| if (!md->si) |
| return -EINVAL; |
| |
| rc = pt_setup_input_device(dev); |
| |
| _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, PT_MT_NAME, |
| pt_setup_input_attention, 0); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_mt_probe |
| * |
| * SUMMARY: The probe function for touch input device |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| int pt_mt_probe(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_mt_data *md = &cd->md; |
| struct pt_platform_data *pdata = dev_get_platdata(dev); |
| struct pt_mt_platform_data *mt_pdata; |
| int rc = 0; |
| |
| pt_debug(dev, DL_INFO, |
| "%s: >>>>>> Register MT <<<<<<\n", __func__); |
| if (!pdata || !pdata->mt_pdata) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Missing platform data\n", __func__); |
| rc = -ENODEV; |
| goto error_no_pdata; |
| } |
| mt_pdata = pdata->mt_pdata; |
| |
| pt_init_function_ptrs(md); |
| |
| mutex_init(&md->mt_lock); |
| md->dev = dev; |
| md->pdata = mt_pdata; |
| |
| /* Create the input device and register it. */ |
| pt_debug(dev, DL_INFO, |
| "%s: Create the input device and register it\n", __func__); |
| md->input = input_allocate_device(); |
| if (!md->input) { |
| pt_debug(dev, DL_ERROR, "%s: Error, failed to allocate input device\n", |
| __func__); |
| rc = -ENODEV; |
| goto error_alloc_failed; |
| } else |
| md->input_device_allocated = true; |
| |
| if (md->pdata->inp_dev_name) |
| md->input->name = md->pdata->inp_dev_name; |
| else |
| md->input->name = PT_MT_NAME; |
| scnprintf(md->phys, sizeof(md->phys), "%s/input%d", dev_name(dev), |
| cd->phys_num++); |
| md->input->phys = md->phys; |
| md->input->dev.parent = md->dev; |
| md->input->open = pt_mt_open; |
| md->input->close = pt_mt_close; |
| input_set_drvdata(md->input, md); |
| |
| /* get sysinfo */ |
| md->si = _pt_request_sysinfo(dev); |
| |
| if (md->si) { |
| rc = pt_setup_input_device(dev); |
| if (rc) |
| goto error_init_input; |
| } else { |
| pt_debug(dev, DL_ERROR, "%s: Fail get sysinfo pointer from core p=%p\n", |
| __func__, md->si); |
| _pt_subscribe_attention(dev, PT_ATTEN_STARTUP, |
| PT_MT_NAME, pt_setup_input_attention, 0); |
| } |
| |
| return 0; |
| |
| error_init_input: |
| input_free_device(md->input); |
| md->input_device_allocated = false; |
| error_alloc_failed: |
| error_no_pdata: |
| pt_debug(dev, DL_ERROR, "%s failed.\n", __func__); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_mt_release |
| * |
| * SUMMARY: The release function for touch input device |
| * |
| * RETURN: |
| * 0 = success |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| int pt_mt_release(struct device *dev) |
| { |
| struct pt_core_data *cd; |
| struct pt_mt_data *md; |
| |
| /* Ensure valid pointers before de-referencing them */ |
| if (dev) { |
| cd = dev_get_drvdata(dev); |
| if (cd) |
| md = &cd->md; |
| else |
| return 0; |
| } else { |
| return 0; |
| } |
| |
| /* |
| * Second call this function may cause kernel panic if probe fail. |
| * Use input_device_registered & input_device_allocated variable to |
| * avoid unregister or free unavailable devive. |
| */ |
| if (md && md->input_device_registered) { |
| md->input_device_registered = false; |
| input_unregister_device(md->input); |
| /* Unregistering device will free the device too */ |
| md->input_device_allocated = false; |
| } else if (md && md->input_device_allocated) { |
| md->input_device_allocated = false; |
| input_free_device(md->input); |
| _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, |
| PT_MT_NAME, pt_setup_input_attention, 0); |
| } |
| |
| return 0; |
| } |