/* * cyttsp5_proximity.c * Parade TrueTouch(TM) Standard Product V5 Proximity Module. * For use with Parade touchscreen controllers. * Supported parts include: * CYTMA5XX * CYTMA448 * CYTMA445A * CYTT21XXX * CYTT31XXX * * Copyright (C) 2015 Parade Technologies * Copyright (C) 2013-2015 Cypress Semiconductor * * 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 * */ #include "cyttsp5_regs.h" #define CYTTSP5_PROXIMITY_NAME "cyttsp5_proximity" /* Timeout value in ms. */ #define CYTTSP5_PROXIMITY_REQUEST_EXCLUSIVE_TIMEOUT 1000 #define CYTTSP5_PROXIMITY_ON 0 #define CYTTSP5_PROXIMITY_OFF 1 static inline struct cyttsp5_proximity_data *get_prox_data(struct device *dev) { struct cyttsp5_core_data *cd = dev_get_drvdata(dev); return &cd->pd; } static void cyttsp5_report_proximity(struct cyttsp5_proximity_data *pd, bool on) { int val = on ? CYTTSP5_PROXIMITY_ON : CYTTSP5_PROXIMITY_OFF; input_report_abs(pd->input, ABS_DISTANCE, val); input_sync(pd->input); } static void cyttsp5_get_touch_axis(struct cyttsp5_proximity_data *pd, int *axis, int size, int max, u8 *xy_data, int bofs) { int nbyte; int next; for (nbyte = 0, *axis = 0, next = 0; nbyte < size; nbyte++) { dev_vdbg(pd->dev, "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p xy_data[%d]=%02X(%d) bofs=%d\n", __func__, *axis, *axis, size, max, xy_data, next, xy_data[next], xy_data[next], bofs); *axis = *axis + ((xy_data[next] >> bofs) << (nbyte * 8)); next++; } *axis &= max - 1; dev_vdbg(pd->dev, "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p xy_data[%d]=%02X(%d)\n", __func__, *axis, *axis, size, max, xy_data, next, xy_data[next], xy_data[next]); } static void cyttsp5_get_touch_hdr(struct cyttsp5_proximity_data *pd, struct cyttsp5_touch *touch, u8 *xy_mode) { struct device *dev = pd->dev; struct cyttsp5_sysinfo *si = pd->si; enum cyttsp5_tch_hdr hdr; for (hdr = CY_TCH_TIME; hdr < CY_TCH_NUM_HDR; hdr++) { if (!si->tch_hdr[hdr].report) continue; cyttsp5_get_touch_axis(pd, &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); dev_vdbg(dev, "%s: get %s=%04X(%d)\n", __func__, cyttsp5_tch_hdr_string[hdr], touch->hdr[hdr], touch->hdr[hdr]); } } static void cyttsp5_get_touch(struct cyttsp5_proximity_data *pd, struct cyttsp5_touch *touch, u8 *xy_data) { struct device *dev = pd->dev; struct cyttsp5_sysinfo *si = pd->si; enum cyttsp5_tch_abs abs; for (abs = CY_TCH_X; abs < CY_TCH_NUM_ABS; abs++) { if (!si->tch_abs[abs].report) continue; cyttsp5_get_touch_axis(pd, &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); dev_vdbg(dev, "%s: get %s=%04X(%d)\n", __func__, cyttsp5_tch_abs_string[abs], touch->abs[abs], touch->abs[abs]); } dev_vdbg(dev, "%s: x=%04X(%d) y=%04X(%d)\n", __func__, touch->abs[CY_TCH_X], touch->abs[CY_TCH_X], touch->abs[CY_TCH_Y], touch->abs[CY_TCH_Y]); } static void cyttsp5_get_proximity_touch(struct cyttsp5_proximity_data *pd, struct cyttsp5_touch *tch, int num_cur_tch) { struct cyttsp5_sysinfo *si = pd->si; int i; for (i = 0; i < num_cur_tch; i++) { cyttsp5_get_touch(pd, tch, si->xy_data + (i * si->desc.tch_record_size)); /* Check for proximity event */ if (tch->abs[CY_TCH_O] == CY_OBJ_PROXIMITY) { if (tch->abs[CY_TCH_E] == CY_EV_TOUCHDOWN) cyttsp5_report_proximity(pd, true); else if (tch->abs[CY_TCH_E] == CY_EV_LIFTOFF) cyttsp5_report_proximity(pd, false); break; } } } /* read xy_data for all current touches */ static int cyttsp5_xy_worker(struct cyttsp5_proximity_data *pd) { struct device *dev = pd->dev; struct cyttsp5_sysinfo *si = pd->si; struct cyttsp5_touch tch; u8 num_cur_tch; cyttsp5_get_touch_hdr(pd, &tch, si->xy_mode + 3); num_cur_tch = tch.hdr[CY_TCH_NUM]; if (num_cur_tch > si->sensing_conf_data.max_tch) { dev_err(dev, "%s: Num touch err detected (n=%d)\n", __func__, num_cur_tch); num_cur_tch = si->sensing_conf_data.max_tch; } if (tch.hdr[CY_TCH_LO]) dev_dbg(dev, "%s: Large area detected\n", __func__); /* extract xy_data for all currently reported touches */ dev_vdbg(dev, "%s: extract data num_cur_rec=%d\n", __func__, num_cur_tch); if (num_cur_tch) cyttsp5_get_proximity_touch(pd, &tch, num_cur_tch); else cyttsp5_report_proximity(pd, false); return 0; } static int cyttsp5_proximity_attention(struct device *dev) { struct cyttsp5_proximity_data *pd = get_prox_data(dev); int rc = 0; if (pd->si->xy_mode[2] != pd->si->desc.tch_report_id) return 0; mutex_lock(&pd->prox_lock); rc = cyttsp5_xy_worker(pd); mutex_unlock(&pd->prox_lock); if (rc < 0) dev_err(dev, "%s: xy_worker error r=%d\n", __func__, rc); return rc; } static int cyttsp5_startup_attention(struct device *dev) { struct cyttsp5_proximity_data *pd = get_prox_data(dev); mutex_lock(&pd->prox_lock); cyttsp5_report_proximity(pd, false); mutex_unlock(&pd->prox_lock); return 0; } static int _cyttsp5_set_proximity_via_touchmode_enabled( struct cyttsp5_proximity_data *pd, bool enable) { struct device *dev = pd->dev; u32 touchmode_enabled; int rc; rc = cyttsp5_request_nonhid_get_param(dev, 0, CY_RAM_ID_TOUCHMODE_ENABLED, &touchmode_enabled); if (rc) return rc; if (enable) touchmode_enabled |= 0x80; else touchmode_enabled &= 0x7F; rc = cyttsp5_request_nonhid_set_param(dev, 0, CY_RAM_ID_TOUCHMODE_ENABLED, touchmode_enabled, CY_RAM_ID_TOUCHMODE_ENABLED_SIZE); return rc; } static int _cyttsp5_set_proximity_via_proximity_enable( struct cyttsp5_proximity_data *pd, bool enable) { struct device *dev = pd->dev; u32 proximity_enable; int rc; rc = cyttsp5_request_nonhid_get_param(dev, 0, CY_RAM_ID_PROXIMITY_ENABLE, &proximity_enable); if (rc) return rc; if (enable) proximity_enable |= 0x01; else proximity_enable &= 0xFE; rc = cyttsp5_request_nonhid_set_param(dev, 0, CY_RAM_ID_PROXIMITY_ENABLE, proximity_enable, CY_RAM_ID_PROXIMITY_ENABLE_SIZE); return rc; } static int _cyttsp5_set_proximity(struct cyttsp5_proximity_data *pd, bool enable) { if (!IS_PIP_VER_GE(pd->si, 1, 4)) return _cyttsp5_set_proximity_via_touchmode_enabled(pd, enable); return _cyttsp5_set_proximity_via_proximity_enable(pd, enable); } static int _cyttsp5_proximity_enable(struct cyttsp5_proximity_data *pd) { struct device *dev = pd->dev; int rc = 0; pm_runtime_get_sync(dev); rc = cyttsp5_request_exclusive(dev, CYTTSP5_PROXIMITY_REQUEST_EXCLUSIVE_TIMEOUT); if (rc < 0) { dev_err(dev, "%s: Error on request exclusive r=%d\n", __func__, rc); goto exit; } rc = _cyttsp5_set_proximity(pd, true); if (rc < 0) { dev_err(dev, "%s: Error on request enable proximity scantype r=%d\n", __func__, rc); goto exit_release; } dev_vdbg(dev, "%s: setup subscriptions\n", __func__); /* set up touch call back */ _cyttsp5_subscribe_attention(dev, CY_ATTEN_IRQ, CYTTSP5_PROXIMITY_NAME, cyttsp5_proximity_attention, CY_MODE_OPERATIONAL); /* set up startup call back */ _cyttsp5_subscribe_attention(dev, CY_ATTEN_STARTUP, CYTTSP5_PROXIMITY_NAME, cyttsp5_startup_attention, 0); exit_release: cyttsp5_release_exclusive(dev); exit: return rc; } static int _cyttsp5_proximity_disable(struct cyttsp5_proximity_data *pd, bool force) { struct device *dev = pd->dev; int rc = 0; rc = cyttsp5_request_exclusive(dev, CYTTSP5_PROXIMITY_REQUEST_EXCLUSIVE_TIMEOUT); if (rc < 0) { dev_err(dev, "%s: Error on request exclusive r=%d\n", __func__, rc); goto exit; } rc = _cyttsp5_set_proximity(pd, false); if (rc < 0) { dev_err(dev, "%s: Error on request disable proximity scan r=%d\n", __func__, rc); goto exit_release; } exit_release: cyttsp5_release_exclusive(dev); exit: if (!rc || force) { _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_IRQ, CYTTSP5_PROXIMITY_NAME, cyttsp5_proximity_attention, CY_MODE_OPERATIONAL); _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_STARTUP, CYTTSP5_PROXIMITY_NAME, cyttsp5_startup_attention, 0); } pm_runtime_put(dev); return rc; } static ssize_t cyttsp5_proximity_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct cyttsp5_proximity_data *pd = get_prox_data(dev); int val = 0; mutex_lock(&pd->sysfs_lock); val = pd->enable_count; mutex_unlock(&pd->sysfs_lock); return scnprintf(buf, CY_MAX_PRBUF_SIZE, "%d\n", val); } static ssize_t cyttsp5_proximity_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct cyttsp5_proximity_data *pd = get_prox_data(dev); unsigned long value; int rc; rc = kstrtoul(buf, 10, &value); if (rc < 0 || (value != 0 && value != 1)) { dev_err(dev, "%s: Invalid value\n", __func__); return -EINVAL; } mutex_lock(&pd->sysfs_lock); if (value) { if (pd->enable_count++) { dev_vdbg(dev, "%s: '%s' already enabled\n", __func__, pd->input->name); } else { rc = _cyttsp5_proximity_enable(pd); if (rc) pd->enable_count--; } } else { if (--pd->enable_count) { if (pd->enable_count < 0) { dev_err(dev, "%s: '%s' unbalanced disable\n", __func__, pd->input->name); pd->enable_count = 0; } } else { rc = _cyttsp5_proximity_disable(pd, false); if (rc) pd->enable_count++; } } mutex_unlock(&pd->sysfs_lock); if (rc) return rc; return size; } static DEVICE_ATTR(prox_enable, S_IRUSR | S_IWUSR, cyttsp5_proximity_enable_show, cyttsp5_proximity_enable_store); static int cyttsp5_setup_input_device_and_sysfs(struct device *dev) { struct cyttsp5_proximity_data *pd = get_prox_data(dev); int signal = CY_IGNORE_VALUE; int i; int rc; rc = device_create_file(dev, &dev_attr_prox_enable); if (rc) { dev_err(dev, "%s: Error, could not create enable\n", __func__); goto exit; } dev_vdbg(dev, "%s: Initialize event signals\n", __func__); __set_bit(EV_ABS, pd->input->evbit); /* set event signal capabilities */ for (i = 0; i < NUM_SIGNALS(pd->pdata->frmwrk); i++) { signal = PARAM_SIGNAL(pd->pdata->frmwrk, i); if (signal != CY_IGNORE_VALUE) { input_set_abs_params(pd->input, signal, PARAM_MIN(pd->pdata->frmwrk, i), PARAM_MAX(pd->pdata->frmwrk, i), PARAM_FUZZ(pd->pdata->frmwrk, i), PARAM_FLAT(pd->pdata->frmwrk, i)); } } rc = input_register_device(pd->input); if (rc) { dev_err(dev, "%s: Error, failed register input device r=%d\n", __func__, rc); goto unregister_enable; } pd->input_device_registered = true; return rc; unregister_enable: device_remove_file(dev, &dev_attr_prox_enable); exit: return rc; } static int cyttsp5_setup_input_attention(struct device *dev) { struct cyttsp5_proximity_data *pd = get_prox_data(dev); int rc; pd->si = _cyttsp5_request_sysinfo(dev); if (!pd->si) return -EINVAL; rc = cyttsp5_setup_input_device_and_sysfs(dev); if (!rc) rc = _cyttsp5_set_proximity(pd, false); _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_STARTUP, CYTTSP5_PROXIMITY_NAME, cyttsp5_setup_input_attention, 0); return rc; } int cyttsp5_proximity_probe(struct device *dev) { struct cyttsp5_core_data *cd = dev_get_drvdata(dev); struct cyttsp5_proximity_data *pd = &cd->pd; struct cyttsp5_platform_data *pdata = dev_get_platdata(dev); struct cyttsp5_proximity_platform_data *prox_pdata; int rc = 0; if (!pdata || !pdata->prox_pdata) { dev_err(dev, "%s: Missing platform data\n", __func__); rc = -ENODEV; goto error_no_pdata; } prox_pdata = pdata->prox_pdata; mutex_init(&pd->prox_lock); mutex_init(&pd->sysfs_lock); pd->dev = dev; pd->pdata = prox_pdata; /* Create the input device and register it. */ dev_vdbg(dev, "%s: Create the input device and register it\n", __func__); pd->input = input_allocate_device(); if (!pd->input) { dev_err(dev, "%s: Error, failed to allocate input device\n", __func__); rc = -ENODEV; goto error_alloc_failed; } if (pd->pdata->inp_dev_name) pd->input->name = pd->pdata->inp_dev_name; else pd->input->name = CYTTSP5_PROXIMITY_NAME; scnprintf(pd->phys, sizeof(pd->phys), "%s/input%d", dev_name(dev), cd->phys_num++); pd->input->phys = pd->phys; pd->input->dev.parent = pd->dev; input_set_drvdata(pd->input, pd); /* get sysinfo */ pd->si = _cyttsp5_request_sysinfo(dev); if (pd->si) { rc = cyttsp5_setup_input_device_and_sysfs(dev); if (rc) goto error_init_input; rc = _cyttsp5_set_proximity(pd, false); } else { dev_err(dev, "%s: Fail get sysinfo pointer from core p=%p\n", __func__, pd->si); _cyttsp5_subscribe_attention(dev, CY_ATTEN_STARTUP, CYTTSP5_PROXIMITY_NAME, cyttsp5_setup_input_attention, 0); } return 0; error_init_input: input_free_device(pd->input); error_alloc_failed: error_no_pdata: dev_err(dev, "%s failed.\n", __func__); return rc; } int cyttsp5_proximity_release(struct device *dev) { struct cyttsp5_proximity_data *pd = get_prox_data(dev); if (pd->input_device_registered) { /* Disable proximity sensing */ mutex_lock(&pd->sysfs_lock); if (pd->enable_count) _cyttsp5_proximity_disable(pd, true); mutex_unlock(&pd->sysfs_lock); device_remove_file(dev, &dev_attr_prox_enable); input_unregister_device(pd->input); } else { input_free_device(pd->input); _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_STARTUP, CYTTSP5_PROXIMITY_NAME, cyttsp5_setup_input_attention, 0); } return 0; }