/* * Synaptics DSX touchscreen driver * * Copyright (C) 2012 Synaptics Incorporated * * Copyright (C) 2012 Alexandra Chin * Copyright (C) 2012 Scott Lin * * 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. */ #include #include #include #include #include #include #include #include #include "synaptics_dsx_core.h" #define PROX_PHYS_NAME "synaptics_dsx/input1" #define HOVER_Z_MAX (255) #define HOVERING_FINGER_EN (1 << 4) static ssize_t synaptics_rmi4_hover_finger_en_show(struct device *dev, struct device_attribute *attr, char *buf); static ssize_t synaptics_rmi4_hover_finger_en_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); static struct device_attribute attrs[] = { __ATTR(hover_finger_en, (S_IRUGO | S_IWUGO), synaptics_rmi4_hover_finger_en_show, synaptics_rmi4_hover_finger_en_store), }; struct synaptics_rmi4_f12_query_5 { union { struct { unsigned char size_of_query6; struct { unsigned char ctrl0_is_present:1; unsigned char ctrl1_is_present:1; unsigned char ctrl2_is_present:1; unsigned char ctrl3_is_present:1; unsigned char ctrl4_is_present:1; unsigned char ctrl5_is_present:1; unsigned char ctrl6_is_present:1; unsigned char ctrl7_is_present:1; } __packed; struct { unsigned char ctrl8_is_present:1; unsigned char ctrl9_is_present:1; unsigned char ctrl10_is_present:1; unsigned char ctrl11_is_present:1; unsigned char ctrl12_is_present:1; unsigned char ctrl13_is_present:1; unsigned char ctrl14_is_present:1; unsigned char ctrl15_is_present:1; } __packed; struct { unsigned char ctrl16_is_present:1; unsigned char ctrl17_is_present:1; unsigned char ctrl18_is_present:1; unsigned char ctrl19_is_present:1; unsigned char ctrl20_is_present:1; unsigned char ctrl21_is_present:1; unsigned char ctrl22_is_present:1; unsigned char ctrl23_is_present:1; } __packed; }; unsigned char data[4]; }; }; struct synaptics_rmi4_f12_query_8 { union { struct { unsigned char size_of_query9; struct { unsigned char data0_is_present:1; unsigned char data1_is_present:1; unsigned char data2_is_present:1; unsigned char data3_is_present:1; unsigned char data4_is_present:1; unsigned char data5_is_present:1; unsigned char data6_is_present:1; unsigned char data7_is_present:1; } __packed; }; unsigned char data[2]; }; }; struct prox_finger_data { union { struct { unsigned char object_type_and_status; unsigned char x_lsb; unsigned char x_msb; unsigned char y_lsb; unsigned char y_msb; unsigned char z; } __packed; unsigned char proximity_data[6]; }; }; struct synaptics_rmi4_prox_handle { bool hover_finger_present; bool hover_finger_en; unsigned char intr_mask; unsigned short query_base_addr; unsigned short control_base_addr; unsigned short data_base_addr; unsigned short command_base_addr; unsigned short hover_finger_en_addr; unsigned short hover_finger_data_addr; struct input_dev *prox_dev; struct prox_finger_data *finger_data; struct synaptics_rmi4_data *rmi4_data; }; static struct synaptics_rmi4_prox_handle *prox; DECLARE_COMPLETION(prox_remove_complete); static void prox_hover_finger_lift(void) { input_report_key(prox->prox_dev, BTN_TOUCH, 0); input_report_key(prox->prox_dev, BTN_TOOL_FINGER, 0); input_sync(prox->prox_dev); prox->hover_finger_present = false; return; } static void prox_hover_finger_report(void) { int retval; int x; int y; int z; struct prox_finger_data *data; struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; data = prox->finger_data; retval = synaptics_rmi4_reg_read(rmi4_data, prox->hover_finger_data_addr, data->proximity_data, sizeof(data->proximity_data)); if (retval < 0) { dev_err(rmi4_data->pdev->dev.parent, "%s: Failed to read hovering finger data\n", __func__); return; } if (data->object_type_and_status != F12_HOVERING_FINGER_STATUS) { if (prox->hover_finger_present) prox_hover_finger_lift(); return; } x = (data->x_msb << 8) | (data->x_lsb); y = (data->y_msb << 8) | (data->y_lsb); z = HOVER_Z_MAX - data->z; input_report_key(prox->prox_dev, BTN_TOUCH, 0); input_report_key(prox->prox_dev, BTN_TOOL_FINGER, 1); input_report_abs(prox->prox_dev, ABS_X, x); input_report_abs(prox->prox_dev, ABS_Y, y); input_report_abs(prox->prox_dev, ABS_DISTANCE, z); input_sync(prox->prox_dev); dev_dbg(rmi4_data->pdev->dev.parent, "%s: x = %d y = %d z = %d\n", __func__, x, y, z); prox->hover_finger_present = true; return; } static int prox_set_hover_finger_en(void) { int retval; unsigned char object_report_enable; struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; retval = synaptics_rmi4_reg_read(rmi4_data, prox->hover_finger_en_addr, &object_report_enable, sizeof(object_report_enable)); if (retval < 0) { dev_err(rmi4_data->pdev->dev.parent, "%s: Failed to read from object report enable register\n", __func__); return retval; } if (prox->hover_finger_en) object_report_enable |= HOVERING_FINGER_EN; else object_report_enable &= ~HOVERING_FINGER_EN; retval = synaptics_rmi4_reg_write(rmi4_data, prox->hover_finger_en_addr, &object_report_enable, sizeof(object_report_enable)); if (retval < 0) { dev_err(rmi4_data->pdev->dev.parent, "%s: Failed to write to object report enable register\n", __func__); return retval; } return 0; } static void prox_set_params(void) { input_set_abs_params(prox->prox_dev, ABS_X, 0, prox->rmi4_data->sensor_max_x, 0, 0); input_set_abs_params(prox->prox_dev, ABS_Y, 0, prox->rmi4_data->sensor_max_y, 0, 0); input_set_abs_params(prox->prox_dev, ABS_DISTANCE, 0, HOVER_Z_MAX, 0, 0); return; } static int prox_reg_init(void) { int retval; unsigned char ctrl_23_offset; unsigned char data_1_offset; struct synaptics_rmi4_f12_query_5 query_5; struct synaptics_rmi4_f12_query_8 query_8; struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; retval = synaptics_rmi4_reg_read(rmi4_data, prox->query_base_addr + 5, query_5.data, sizeof(query_5.data)); if (retval < 0) return retval; ctrl_23_offset = query_5.ctrl0_is_present + query_5.ctrl1_is_present + query_5.ctrl2_is_present + query_5.ctrl3_is_present + query_5.ctrl4_is_present + query_5.ctrl5_is_present + query_5.ctrl6_is_present + query_5.ctrl7_is_present + query_5.ctrl8_is_present + query_5.ctrl9_is_present + query_5.ctrl10_is_present + query_5.ctrl11_is_present + query_5.ctrl12_is_present + query_5.ctrl13_is_present + query_5.ctrl14_is_present + query_5.ctrl15_is_present + query_5.ctrl16_is_present + query_5.ctrl17_is_present + query_5.ctrl18_is_present + query_5.ctrl19_is_present + query_5.ctrl20_is_present + query_5.ctrl21_is_present + query_5.ctrl22_is_present; prox->hover_finger_en_addr = prox->control_base_addr + ctrl_23_offset; retval = synaptics_rmi4_reg_read(rmi4_data, prox->query_base_addr + 8, query_8.data, sizeof(query_8.data)); if (retval < 0) return retval; data_1_offset = query_8.data0_is_present; prox->hover_finger_data_addr = prox->data_base_addr + data_1_offset; return retval; } static int prox_scan_pdt(void) { int retval; unsigned char ii; unsigned char page; unsigned char intr_count = 0; unsigned char intr_off; unsigned char intr_src; unsigned short addr; struct synaptics_rmi4_fn_desc fd; struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; for (page = 0; page < PAGES_TO_SERVICE; page++) { for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { addr |= (page << 8); retval = synaptics_rmi4_reg_read(rmi4_data, addr, (unsigned char *)&fd, sizeof(fd)); if (retval < 0) return retval; addr &= ~(MASK_8BIT << 8); if (fd.fn_number) { dev_dbg(rmi4_data->pdev->dev.parent, "%s: Found F%02x\n", __func__, fd.fn_number); switch (fd.fn_number) { case SYNAPTICS_RMI4_F12: goto f12_found; break; } } else { break; } intr_count += (fd.intr_src_count & MASK_3BIT); } } dev_err(rmi4_data->pdev->dev.parent, "%s: Failed to find F12\n", __func__); return -EINVAL; f12_found: prox->query_base_addr = fd.query_base_addr | (page << 8); prox->control_base_addr = fd.ctrl_base_addr | (page << 8); prox->data_base_addr = fd.data_base_addr | (page << 8); prox->command_base_addr = fd.cmd_base_addr | (page << 8); retval = prox_reg_init(); if (retval < 0) { dev_err(rmi4_data->pdev->dev.parent, "%s: Failed to initialize proximity registers\n", __func__); return retval; } prox->intr_mask = 0; intr_src = fd.intr_src_count; intr_off = intr_count % 8; for (ii = intr_off; ii < ((intr_src & MASK_3BIT) + intr_off); ii++) { prox->intr_mask |= 1 << ii; } rmi4_data->intr_mask[0] |= prox->intr_mask; addr = rmi4_data->f01_ctrl_base_addr + 1; retval = synaptics_rmi4_reg_write(rmi4_data, addr, &(rmi4_data->intr_mask[0]), sizeof(rmi4_data->intr_mask[0])); if (retval < 0) { dev_err(rmi4_data->pdev->dev.parent, "%s: Failed to set interrupt enable bit\n", __func__); return retval; } return 0; } static ssize_t synaptics_rmi4_hover_finger_en_show(struct device *dev, struct device_attribute *attr, char *buf) { if (!prox) return -ENODEV; return snprintf(buf, PAGE_SIZE, "%u\n", prox->hover_finger_en); } static ssize_t synaptics_rmi4_hover_finger_en_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int retval; unsigned int input; struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; if (!prox) return -ENODEV; if (sscanf(buf, "%x", &input) != 1) return -EINVAL; if (input == 1) prox->hover_finger_en = true; else if (input == 0) prox->hover_finger_en = false; else return -EINVAL; retval = prox_set_hover_finger_en(); if (retval < 0) { dev_err(rmi4_data->pdev->dev.parent, "%s: Failed to change hovering finger enable setting\n", __func__); return retval; } return count; } int synaptics_rmi4_prox_hover_finger_en(bool enable) { int retval; if (!prox) return -ENODEV; prox->hover_finger_en = enable; retval = prox_set_hover_finger_en(); if (retval < 0) return retval; return 0; } EXPORT_SYMBOL(synaptics_rmi4_prox_hover_finger_en); static void synaptics_rmi4_prox_attn(struct synaptics_rmi4_data *rmi4_data, unsigned char intr_mask) { if (!prox) return; if (prox->intr_mask & intr_mask) prox_hover_finger_report(); return; } static int synaptics_rmi4_prox_init(struct synaptics_rmi4_data *rmi4_data) { int retval; unsigned char attr_count; prox = kzalloc(sizeof(*prox), GFP_KERNEL); if (!prox) { dev_err(rmi4_data->pdev->dev.parent, "%s: Failed to alloc mem for prox\n", __func__); retval = -ENOMEM; goto exit; } prox->finger_data = kzalloc(sizeof(*(prox->finger_data)), GFP_KERNEL); if (!prox->finger_data) { dev_err(rmi4_data->pdev->dev.parent, "%s: Failed to alloc mem for finger_data\n", __func__); retval = -ENOMEM; goto exit_free_prox; } prox->rmi4_data = rmi4_data; retval = prox_scan_pdt(); if (retval < 0) goto exit_free_finger_data; prox->hover_finger_en = true; retval = prox_set_hover_finger_en(); if (retval < 0) return retval; prox->prox_dev = input_allocate_device(); if (prox->prox_dev == NULL) { dev_err(rmi4_data->pdev->dev.parent, "%s: Failed to allocate proximity device\n", __func__); retval = -ENOMEM; goto exit_free_finger_data; } prox->prox_dev->name = PLATFORM_DRIVER_NAME; prox->prox_dev->phys = PROX_PHYS_NAME; prox->prox_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT; prox->prox_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION; prox->prox_dev->dev.parent = rmi4_data->pdev->dev.parent; input_set_drvdata(prox->prox_dev, rmi4_data); set_bit(EV_KEY, prox->prox_dev->evbit); set_bit(EV_ABS, prox->prox_dev->evbit); set_bit(BTN_TOUCH, prox->prox_dev->keybit); set_bit(BTN_TOOL_FINGER, prox->prox_dev->keybit); #ifdef INPUT_PROP_DIRECT set_bit(INPUT_PROP_DIRECT, prox->prox_dev->propbit); #endif prox_set_params(); retval = input_register_device(prox->prox_dev); if (retval) { dev_err(rmi4_data->pdev->dev.parent, "%s: Failed to register proximity device\n", __func__); goto exit_free_input_device; } for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { retval = sysfs_create_file(&rmi4_data->input_dev->dev.kobj, &attrs[attr_count].attr); if (retval < 0) { dev_err(rmi4_data->pdev->dev.parent, "%s: Failed to create sysfs attributes\n", __func__); goto exit_free_sysfs; } } return 0; exit_free_sysfs: for (attr_count--; attr_count >= 0; attr_count--) { sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, &attrs[attr_count].attr); } input_unregister_device(prox->prox_dev); prox->prox_dev = NULL; exit_free_input_device: if (prox->prox_dev) input_free_device(prox->prox_dev); exit_free_finger_data: kfree(prox->finger_data); exit_free_prox: kfree(prox); prox = NULL; exit: return retval; } static void synaptics_rmi4_prox_remove(struct synaptics_rmi4_data *rmi4_data) { unsigned char attr_count; if (!prox) goto exit; for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, &attrs[attr_count].attr); } input_unregister_device(prox->prox_dev); kfree(prox->finger_data); kfree(prox); prox = NULL; exit: complete(&prox_remove_complete); return; } static void synaptics_rmi4_prox_reset(struct synaptics_rmi4_data *rmi4_data) { if (!prox) return; prox_hover_finger_lift(); prox_scan_pdt(); prox_set_hover_finger_en(); prox_set_params(); return; } static void synaptics_rmi4_prox_reinit(struct synaptics_rmi4_data *rmi4_data) { if (!prox) return; prox_hover_finger_lift(); prox_set_hover_finger_en(); return; } static void synaptics_rmi4_prox_e_suspend(struct synaptics_rmi4_data *rmi4_data) { if (!prox) return; prox_hover_finger_lift(); return; } static void synaptics_rmi4_prox_suspend(struct synaptics_rmi4_data *rmi4_data) { if (!prox) return; prox_hover_finger_lift(); return; } static struct synaptics_rmi4_exp_fn proximity_module = { .fn_type = RMI_PROXIMITY, .init = synaptics_rmi4_prox_init, .remove = synaptics_rmi4_prox_remove, .reset = synaptics_rmi4_prox_reset, .reinit = synaptics_rmi4_prox_reinit, .early_suspend = synaptics_rmi4_prox_e_suspend, .suspend = synaptics_rmi4_prox_suspend, .resume = NULL, .late_resume = NULL, .attn = synaptics_rmi4_prox_attn, }; static int __init rmi4_proximity_module_init(void) { synaptics_rmi4_dsx_new_function(&proximity_module, true); return 0; } static void __exit rmi4_proximity_module_exit(void) { synaptics_rmi4_dsx_new_function(&proximity_module, false); wait_for_completion(&prox_remove_complete); return; } module_init(rmi4_proximity_module_init); module_exit(rmi4_proximity_module_exit); MODULE_AUTHOR("Synaptics, Inc."); MODULE_DESCRIPTION("Synaptics DSX Proximity Module"); MODULE_LICENSE("GPL v2");