| // SPDX-License-Identifier: GPL-2.0-only |
| #ifndef TTDL_KERNEL_SUBMISSION |
| /* |
| * pt_pen.c |
| * Parade TrueTouch(TM) Standard Product CapSense Reports Module. |
| * For use with Parade touchscreen controllers. |
| * Supported parts include: |
| * TMA5XX |
| * TMA448 |
| * TMA445A |
| * TT21XXX |
| * TT31XXX |
| * TT4XXXX |
| * TT7XXX |
| * TC3XXX |
| * |
| * Copyright (C) 2015-2021 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" |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pen_lift_all |
| * |
| * SUMMARY: Reports pen liftoff action |
| * |
| * PARAMETERS: |
| * *pend - pointer to pen data structure |
| ******************************************************************************/ |
| static void pt_pen_lift_all(struct pt_pen_data *pend) |
| { |
| input_report_key(pend->input, BTN_STYLUS, 0); |
| input_report_key(pend->input, BTN_STYLUS2, 0); |
| input_report_key(pend->input, BTN_TOUCH, 0); |
| input_report_key(pend->input, BTN_TOOL_PEN, 0); |
| input_sync(pend->input); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_pen_data |
| * |
| * SUMMARY: Gets axis of pen report |
| * |
| * PARAMETERS: |
| * *pend - pointer to pen data structure |
| * *xy_data - pointer to touch data |
| ******************************************************************************/ |
| static void pt_get_pen(struct pt_pen_data *pend, |
| struct pt_pen *pen, u8 *xy_data) |
| { |
| struct device *dev = pend->dev; |
| struct pt_sysinfo *si = pend->si; |
| enum pt_pen_abs abs; |
| |
| for (abs = PT_PEN_X; abs < PT_PEN_NUM_ABS; abs++) { |
| if (!si->pen_abs[abs].report) |
| continue; |
| pt_get_touch_field(dev, &pen->abs[abs], |
| si->pen_abs[abs].size, |
| si->pen_abs[abs].max, |
| xy_data + si->pen_abs[abs].ofs, |
| si->pen_abs[abs].bofs); |
| |
| /* |
| * The value of TILT takes 2 bytes and will be 0x0000xxxx |
| * after assigning to variable "abs" of type "int" . So the |
| * sign bit needs to be filled if it is negative. |
| */ |
| if (abs == PT_PEN_X_TILT || abs == PT_PEN_Y_TILT) |
| pen->abs[abs] = sign_extend32(pen->abs[abs], 15); |
| |
| pt_debug(dev, DL_DEBUG, "%s: get %s=%04X(%d)\n", |
| __func__, pt_pen_abs_string[abs], |
| pen->abs[abs], pen->abs[abs]); |
| } |
| } |
| |
| |
| /******************************************************************************* |
| * FUNCTION: pt_xy_worker |
| * |
| * SUMMARY: Read xy_data for current pen touch |
| * |
| * RETURN: |
| * 0 = success |
| * |
| * PARAMETERS: |
| * *pend - pointer to pen data structure |
| ******************************************************************************/ |
| static int pt_xy_worker(struct pt_pen_data *pend) |
| { |
| struct pt_sysinfo *si = pend->si; |
| struct pt_pen pen; |
| bool tool; |
| |
| pt_get_pen(pend, &pen, si->xy_data + 3); |
| |
| tool = pen.abs[PT_PEN_IV] ? |
| BTN_TOOL_RUBBER : BTN_TOOL_PEN; |
| |
| input_report_abs(pend->input, ABS_X, pen.abs[PT_PEN_X]); |
| input_report_abs(pend->input, ABS_Y, pen.abs[PT_PEN_Y]); |
| input_report_abs(pend->input, ABS_PRESSURE, pen.abs[PT_PEN_P]); |
| input_report_key(pend->input, BTN_STYLUS, pen.abs[PT_PEN_BS]); |
| |
| if (si->pen_abs[PT_PEN_2ND_BS].report) |
| input_report_key(pend->input, BTN_STYLUS2, |
| pen.abs[PT_PEN_2ND_BS]); |
| |
| input_report_key(pend->input, BTN_TOUCH, pen.abs[PT_PEN_TS]); |
| input_report_key(pend->input, tool, pen.abs[PT_PEN_IR]); |
| |
| if (si->pen_abs[PT_PEN_X_TILT].report) |
| input_report_abs(pend->input, ABS_TILT_X, |
| pen.abs[PT_PEN_X_TILT]); |
| |
| if (si->pen_abs[PT_PEN_Y_TILT].report) |
| input_report_abs(pend->input, ABS_TILT_Y, |
| pen.abs[PT_PEN_Y_TILT]); |
| |
| input_sync(pend->input); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pen_attention |
| * |
| * SUMMARY: Wrapper function for pt_xy_worker() that register to TTDL attention |
| * list. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int pt_pen_attention(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_pen_data *pend = &cd->pend; |
| int rc; |
| |
| if (pend->si->xy_mode[2] != pend->si->desc.pen_report_id) |
| return 0; |
| |
| /* core handles handshake */ |
| mutex_lock(&pend->pen_lock); |
| rc = pt_xy_worker(pend); |
| mutex_unlock(&pend->pen_lock); |
| if (rc < 0) |
| pt_debug(dev, DL_ERROR, |
| "%s: xy_worker error r=%d\n", __func__, rc); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_startup_attention |
| * |
| * SUMMARY: Wrapper function for pt_pen_lift_all() that register to 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_pen_data *pend = &cd->pend; |
| |
| mutex_lock(&pend->pen_lock); |
| pt_pen_lift_all(pend); |
| mutex_unlock(&pend->pen_lock); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pen_suspend_attention |
| * |
| * SUMMARY: Function for pen to enter suspend state that as following steps: |
| * 1) Lift pen touch |
| * 2) Set flag with suspend state |
| * 3) Decrese pm system count |
| * |
| * RETURN: |
| * 0 = success |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int pt_pen_suspend_attention(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_pen_data *pend = &cd->pend; |
| |
| mutex_lock(&pend->pen_lock); |
| pt_pen_lift_all(pend); |
| pend->is_suspended = true; |
| mutex_unlock(&pend->pen_lock); |
| |
| pm_runtime_put(dev); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pen_resume_attention |
| * |
| * SUMMARY: Function for pen 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_pen_resume_attention(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_pen_data *pend = &cd->pend; |
| |
| pm_runtime_get(dev); |
| |
| mutex_lock(&pend->pen_lock); |
| pend->is_suspended = false; |
| mutex_unlock(&pend->pen_lock); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pen_open |
| * |
| * SUMMARY: Open method for input device(pen) that sets up call back |
| * functions to TTDL attention list |
| * |
| * RETURN: |
| * 0 = success |
| * |
| * PARAMETERS: |
| * *input - pointer to input_dev structure |
| ******************************************************************************/ |
| static int pt_pen_open(struct input_dev *input) |
| { |
| struct device *dev = input->dev.parent; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_pen_data *pend = &cd->pend; |
| |
| pm_runtime_get_sync(dev); |
| |
| mutex_lock(&pend->pen_lock); |
| pend->is_suspended = false; |
| mutex_unlock(&pend->pen_lock); |
| |
| pt_debug(dev, DL_INFO, "%s: setup subscriptions\n", __func__); |
| |
| /* set up touch call back */ |
| _pt_subscribe_attention(dev, PT_ATTEN_IRQ, PT_PEN_NAME, |
| pt_pen_attention, PT_MODE_OPERATIONAL); |
| |
| /* set up startup call back */ |
| _pt_subscribe_attention(dev, PT_ATTEN_STARTUP, PT_PEN_NAME, |
| pt_startup_attention, 0); |
| |
| /* set up suspend call back */ |
| _pt_subscribe_attention(dev, PT_ATTEN_SUSPEND, PT_PEN_NAME, |
| pt_pen_suspend_attention, 0); |
| |
| /* set up resume call back */ |
| _pt_subscribe_attention(dev, PT_ATTEN_RESUME, PT_PEN_NAME, |
| pt_pen_resume_attention, 0); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pen_close |
| * |
| * SUMMARY: Close method for input device(pen) that clears call back |
| * functions from TTDL attention list. |
| * |
| * PARAMETERS: |
| * *input - pointer to input_dev structure |
| ******************************************************************************/ |
| static void pt_pen_close(struct input_dev *input) |
| { |
| struct device *dev = input->dev.parent; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_pen_data *pend = &cd->pend; |
| |
| _pt_unsubscribe_attention(dev, PT_ATTEN_IRQ, PT_PEN_NAME, |
| pt_pen_attention, PT_MODE_OPERATIONAL); |
| |
| _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, PT_PEN_NAME, |
| pt_startup_attention, 0); |
| |
| _pt_unsubscribe_attention(dev, PT_ATTEN_SUSPEND, PT_PEN_NAME, |
| pt_pen_suspend_attention, 0); |
| |
| _pt_unsubscribe_attention(dev, PT_ATTEN_RESUME, PT_PEN_NAME, |
| pt_pen_resume_attention, 0); |
| |
| mutex_lock(&pend->pen_lock); |
| if (!pend->is_suspended) { |
| pm_runtime_put(dev); |
| pend->is_suspended = true; |
| } |
| mutex_unlock(&pend->pen_lock); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_setup_input_device |
| * |
| * SUMMARY: Set up resolution, event signal capabilities and register input |
| * device for pen. |
| * |
| * 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_pen_data *pend = &cd->pend; |
| int i; |
| int rc; |
| u32 usage; |
| int max_x, max_y, max_p, min; |
| |
| pt_debug(dev, DL_INFO, "%s: Initialize event signals\n", |
| __func__); |
| __set_bit(EV_ABS, pend->input->evbit); |
| __set_bit(EV_KEY, pend->input->evbit); |
| |
| for (i = PT_PEN_X; i < PT_PEN_NUM_ABS; i++) { |
| usage = pt_pen_abs_field_map[i]; |
| |
| switch (usage) { |
| case HID_GD_X: |
| max_x = pend->si->sensing_conf_data.res_x; |
| input_set_abs_params(pend->input, |
| ABS_X, 0, max_x, 0, 0); |
| break; |
| case HID_GD_Y: |
| max_y = pend->si->sensing_conf_data.res_y; |
| input_set_abs_params(pend->input, |
| ABS_Y, 0, max_y, 0, 0); |
| break; |
| case HID_DG_TIPPRESSURE: |
| max_p = pend->si->sensing_conf_data.max_z; |
| input_set_abs_params(pend->input, |
| ABS_PRESSURE, 0, max_p, 0, 0); |
| break; |
| case HID_DG_INRANGE: |
| input_set_capability(pend->input, |
| EV_KEY, BTN_TOOL_PEN); |
| break; |
| case HID_DG_INVERT: |
| input_set_capability(pend->input, |
| EV_KEY, BTN_TOOL_RUBBER); |
| break; |
| case HID_DG_TILT_X: |
| max_x = pend->si->pen_abs[i].logical_max; |
| /* |
| * TILT is 16-bit signed value in our case, and TILT Min |
| * is negative. |
| */ |
| min = |
| sign_extend32(pend->si->pen_abs[i].logical_min, 15); |
| input_set_abs_params(pend->input, |
| ABS_TILT_X, min, max_x, 0, 0); |
| break; |
| case HID_DG_TILT_Y: |
| max_x = pend->si->pen_abs[i].logical_max; |
| /* |
| * TILT is 16-bit signed value in our case, and TILT Min |
| * is negative. |
| */ |
| min = |
| sign_extend32(pend->si->pen_abs[i].logical_min, 15); |
| input_set_abs_params(pend->input, |
| ABS_TILT_Y, min, max_x, 0, 0); |
| break; |
| case HID_DG_ERASER: |
| case HID_DG_TIPSWITCH: |
| input_set_capability(pend->input, |
| EV_KEY, BTN_TOUCH); |
| break; |
| case HID_DG_BARRELSWITCH: |
| input_set_capability(pend->input, |
| EV_KEY, BTN_STYLUS); |
| break; |
| case HID_DG_BARRELSWITCH2: |
| input_set_capability(pend->input, |
| EV_KEY, BTN_STYLUS2); |
| break; |
| } |
| } |
| |
| rc = input_register_device(pend->input); |
| if (rc < 0) |
| pt_debug(dev, DL_ERROR, |
| "%s: Error, failed register input device r=%d\n", |
| __func__, rc); |
| else |
| pend->input_device_registered = true; |
| |
| 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_pen_data *pend = &cd->pend; |
| int rc; |
| |
| pend->si = _pt_request_sysinfo(dev); |
| if (!pend->si) |
| return -1; |
| |
| rc = pt_setup_input_device(dev); |
| |
| _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, PT_PEN_NAME, |
| pt_setup_input_attention, 0); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pen_probe |
| * |
| * SUMMARY: The probe function for pen input device |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| int pt_pen_probe(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_pen_data *pend = &cd->pend; |
| struct pt_platform_data *pdata = dev_get_platdata(dev); |
| struct pt_pen_platform_data *pen_pdata; |
| int rc = 0; |
| |
| if (!pdata || !pdata->pen_pdata) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Missing platform data\n", __func__); |
| rc = -ENODEV; |
| goto error_no_pdata; |
| } |
| pen_pdata = pdata->pen_pdata; |
| |
| mutex_init(&pend->pen_lock); |
| pend->dev = dev; |
| pend->pdata = pen_pdata; |
| |
| /* Create the input device and register it. */ |
| pt_debug(dev, DL_INFO, |
| "%s: Create the input device and register it\n", __func__); |
| pend->input = input_allocate_device(); |
| if (!pend->input) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Error, failed to allocate input device\n", |
| __func__); |
| rc = -ENODEV; |
| goto error_alloc_failed; |
| } else |
| pend->input_device_allocated = true; |
| |
| if (pend->pdata->inp_dev_name) |
| pend->input->name = pend->pdata->inp_dev_name; |
| else |
| pend->input->name = PT_PEN_NAME; |
| scnprintf(pend->phys, sizeof(pend->phys), "%s/input%d", dev_name(dev), |
| cd->phys_num++); |
| pend->input->phys = pend->phys; |
| pend->input->dev.parent = pend->dev; |
| pend->input->open = pt_pen_open; |
| pend->input->close = pt_pen_close; |
| input_set_drvdata(pend->input, pend); |
| |
| /* get sysinfo */ |
| pend->si = _pt_request_sysinfo(dev); |
| |
| if (pend->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__, pend->si); |
| _pt_subscribe_attention(dev, PT_ATTEN_STARTUP, |
| PT_PEN_NAME, pt_setup_input_attention, 0); |
| } |
| |
| return 0; |
| |
| error_init_input: |
| input_free_device(pend->input); |
| pend->input_device_allocated = false; |
| error_alloc_failed: |
| error_no_pdata: |
| pt_debug(dev, DL_ERROR, "%s failed.\n", __func__); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pen_release |
| * |
| * SUMMARY: The release function for pen input device |
| * |
| * RETURN: |
| * 0 = success |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| int pt_pen_release(struct device *dev) |
| { |
| struct pt_core_data *cd; |
| struct pt_pen_data *pend; |
| |
| /* Ensure valid pointers before de-referencing them */ |
| if (dev) { |
| cd = dev_get_drvdata(dev); |
| if (cd) |
| pend = &cd->pend; |
| 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 (pend && pend->input_device_registered) { |
| pend->input_device_registered = false; |
| input_unregister_device(pend->input); |
| /* Unregistering device will free the device too */ |
| pend->input_device_allocated = false; |
| } else if (pend && pend->input_device_allocated) { |
| pend->input_device_allocated = false; |
| input_free_device(pend->input); |
| _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, |
| PT_PEN_NAME, pt_setup_input_attention, 0); |
| } |
| |
| return 0; |
| } |
| #endif /*!TTDL_KERNEL_SUBMISSION */ |