diff options
Diffstat (limited to 'drivers/input/touchscreen/vtl/vtl_ts.c')
-rw-r--r-- | drivers/input/touchscreen/vtl/vtl_ts.c | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/vtl/vtl_ts.c b/drivers/input/touchscreen/vtl/vtl_ts.c new file mode 100644 index 000000000000..f7bef9e81158 --- /dev/null +++ b/drivers/input/touchscreen/vtl/vtl_ts.c @@ -0,0 +1,496 @@ +/* + * VTL CTP driver + * + * Copyright (C) 2013 VTL Corporation + * Copyright (C) 2016 Freescale Semiconductor, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/fs.h> +#include <linux/proc_fs.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/gpio.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/pm_qos.h> +#include <linux/slab.h> +#include <linux/types.h> + +#define FORCE_SINGLE_EVENT 1 + +#include "vtl_ts.h" + +#define MIN_X 0x00 +#define MIN_Y 0x00 +#define MAX_X 1023 +#define MAX_Y 767 +#define MAX_AREA 0xff +#define MAX_FINGERS 2 + + +/* Global or static variables */ +struct ts_driver g_driver; + +static struct ts_info g_ts = { + .driver = &g_driver, +}; +static struct ts_info *pg_ts = &g_ts; + +static struct i2c_device_id vtl_ts_id[] = { + { DRIVER_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vtl_ts_id); + + +static int vtl_ts_config(struct ts_info *ts) +{ + struct device *dev; + + DEBUG(); + + dev = &ts->driver->client->dev; + + /* ts config */ + ts->config_info.touch_point_number = TOUCH_POINT_NUM; + + pr_info("Configuring vtl\n"); + ts->config_info.screen_max_x = SCREEN_MAX_X; + ts->config_info.screen_max_y = SCREEN_MAX_y; + return 0; +} + +void vtl_ts_free_gpio(void) +{ + struct ts_info *ts; + + ts = pg_ts; + DEBUG(); + + gpio_free(ts->config_info.irq_gpio_number); +} + +void vtl_ts_hw_reset(void) +{ + struct ts_info *ts; + + ts = pg_ts; + DEBUG(); + + gpio_set_value(ts->config_info.rst_gpio_number, 0); + mdelay(50); + gpio_set_value(ts->config_info.rst_gpio_number, 1); +} + +static irqreturn_t vtl_ts_irq(int irq, void *dev) +{ + struct ts_info *ts; + + ts = pg_ts; + DEBUG(); + + queue_work(ts->driver->workqueue, &ts->driver->event_work); + + return IRQ_HANDLED; +} + +static union ts_xy_data *vtl_read_xy_data(struct ts_info *ts) +{ + struct i2c_msg msgs; + int err; + + DEBUG(); + + msgs.addr = ts->driver->client->addr; + msgs.flags = 0x01; + msgs.len = sizeof(ts->xy_data.buf); + msgs.buf = ts->xy_data.buf; + + err = i2c_transfer(ts->driver->client->adapter, &msgs, 1); + if (err != 1) { + pr_err("___%s:i2c read err___\n", __func__); + return NULL; + } + return &ts->xy_data; +} + +static void vtl_report_xy_coord(struct input_dev *input_dev, + union ts_xy_data *xy_data, + unsigned char touch_point_number) +{ + struct ts_info *ts; + int id; + int sync; + int x, y; + unsigned int press; + static unsigned int release; + + ts = pg_ts; + DEBUG(); + + /* report points */ + sync = 0; press = 0; + for (id = 0; id < touch_point_number; id++) { + if ((xy_data->point[id].xhi != 0xFF) && + (xy_data->point[id].yhi != 0xFF) && + ((xy_data->point[id].status == 1) || + (xy_data->point[id].status == 2))) { + x = (xy_data->point[id].xhi<<4) | + (xy_data->point[id].xlo&0xF); + y = (xy_data->point[id].yhi<<4) | + (xy_data->point[id].ylo&0xF); + + if (ts->config_info.exchange_x_y_flag) + swap(x, y); + + if (ts->config_info.revert_x_flag) + x = ts->config_info.screen_max_x - x; + + if (ts->config_info.revert_y_flag) + y = ts->config_info.screen_max_y - y; +#ifndef FORCE_SINGLE_EVENT + input_mt_slot(input_dev, xy_data->point[id].id - 1); + input_mt_report_slot_state(input_dev, + MT_TOOL_FINGER, true); + input_report_abs(input_dev, ABS_MT_POSITION_X, x); + input_report_abs(input_dev, ABS_MT_POSITION_Y, y); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, 30); + input_report_abs(input_dev, ABS_MT_WIDTH_MAJOR, 128); +#else + input_report_abs(input_dev, ABS_X, x); + input_report_abs(input_dev, ABS_Y, y); + input_report_key(input_dev, BTN_TOUCH, 1); + input_report_abs(input_dev, ABS_PRESSURE, 1); +#endif + sync = 1; + press |= 0x01 << (xy_data->point[id].id - 1); + } + } + + release &= (release ^ press); /*release point flag */ + for (id = 0; id < touch_point_number; id++) { + if (release & (0x01 << id)) { +#ifndef FORCE_SINGLE_EVENT + input_mt_slot(input_dev, id); + input_mt_report_slot_state(input_dev, + MT_TOOL_FINGER, false); +#else + input_report_key(input_dev, BTN_TOUCH, 0); + input_report_abs(input_dev, ABS_PRESSURE, 0); +#endif + sync = 1; + } + + } + release = press; + + if (sync) + input_sync(input_dev); +} + +static void vtl_ts_workfunc(struct work_struct *work) +{ + + union ts_xy_data *xy_data; + struct input_dev *input_dev; + unsigned char touch_point_number; + + DEBUG(); + + input_dev = pg_ts->driver->input_dev; + touch_point_number = pg_ts->config_info.touch_point_number; + + xy_data = vtl_read_xy_data(pg_ts); + if (xy_data != NULL) + vtl_report_xy_coord(input_dev, xy_data, touch_point_number); + else + pr_err("____xy_data error___\n"); +} + +#ifdef CONFIG_PM_SLEEP +int vtl_ts_suspend(struct device *dev) +{ + struct ts_info *ts; + + ts = pg_ts; + DEBUG(); + + disable_irq(ts->config_info.irq_number); + cancel_work_sync(&ts->driver->event_work); + + return 0; +} + +int vtl_ts_resume(struct device *dev) +{ + struct ts_info *ts; + + ts = pg_ts; + DEBUG(); + + /* Hardware reset */ + vtl_ts_hw_reset(); + enable_irq(ts->config_info.irq_number); + + return 0; +} +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void vtl_ts_early_suspend(struct early_suspend *handler) +{ + struct ts_info *ts; + + ts = pg_ts; + DEBUG(); + + vtl_ts_suspend(ts->driver->client, PMSG_SUSPEND); +} + +static void vtl_ts_early_resume(struct early_suspend *handler) +{ + struct ts_info *ts; + + ts = pg_ts; + DEBUG(); + + vtl_ts_resume(ts->driver->client); +} +#endif + +int vtl_ts_remove(struct i2c_client *client) +{ + struct ts_info *ts; + + ts = pg_ts; + DEBUG(); + + /* Driver clean up */ + + free_irq(ts->config_info.irq_number, ts); + vtl_ts_free_gpio(); + +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&ts->driver->early_suspend); +#endif + + cancel_work_sync(&ts->driver->event_work); + destroy_workqueue(ts->driver->workqueue); + + input_unregister_device(ts->driver->input_dev); + input_free_device(ts->driver->input_dev); + + if (ts->driver->proc_entry != NULL) + remove_proc_entry(DRIVER_NAME, NULL); + + return 0; +} + +static int init_input_dev(struct ts_info *ts) +{ + struct input_dev *input_dev; + struct device *dev; + int err; + + DEBUG(); + + dev = &ts->driver->client->dev; + + /* allocate input device */ + ts->driver->input_dev = devm_input_allocate_device(dev); + if (ts->driver->input_dev == NULL) { + dev_err(dev, "Unable to allocate input device for device %s\n", + DRIVER_NAME); + return -1; + } + + input_dev = ts->driver->input_dev; + + input_dev->name = "VTL for wld"; + input_dev->phys = "I2C"; + input_dev->id.bustype = BUS_I2C; + input_dev->id.vendor = 0xaaaa; + input_dev->id.product = 0x5555; + input_dev->id.version = 0x0001; + input_dev->dev.parent = dev; + + /* config input device */ + __set_bit(EV_SYN, input_dev->evbit); + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(EV_ABS, input_dev->evbit); + +#ifdef FORCE_SINGLE_EVENT + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, 1, 0, 0); + input_set_abs_params(input_dev, ABS_X, MIN_X, MAX_X, 0, 0); + input_set_abs_params(input_dev, ABS_Y, MIN_Y, MAX_Y, 0, 0); +#else + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + + input_mt_init_slots(input_dev, TOUCH_POINT_NUM, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, + ts->config_info.screen_max_x, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, + ts->config_info.screen_max_y, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0); +#endif + /* register input device */ + err = input_register_device(input_dev); + if (err) { + dev_err(dev, "Unable to register input device for device %s\n", + DRIVER_NAME); + return -1; + } + + return 0; +} + +int ct36x_test_tp(struct i2c_client *client) +{ + struct i2c_msg msgs; + char buf; + + msgs.addr = 0x7F; + msgs.flags = 0x01; + msgs.len = 1; + msgs.buf = &buf; + + if (i2c_transfer(client->adapter, &msgs, 1) != 1) + return -1; + + return 0; +} + +int vtl_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int err = -1; + struct ts_info *ts; + struct device *dev; + + ts = pg_ts; + ts->driver->client = client; + dev = &ts->driver->client->dev; + + /*Probing TouchScreen*/ + pr_info("Probing vtl touchscreen, touchscreen node found\n"); + if (ct36x_test_tp(client) < 0) { + pr_err("vtl tp not found\n"); + goto ERR_TS_CONFIG; + } + + /* Request platform resources (gpio/interrupt pins) */ + err = vtl_ts_config(ts); + if (err) { + dev_err(dev, "VTL touch screen config Failed.\n"); + goto ERR_TS_CONFIG; + } + + /*Requestion GPIO*/ + ts->config_info.rst_gpio_number = of_get_gpio(client->dev.of_node, 0); + if (gpio_is_valid(ts->config_info.rst_gpio_number)) { + err = devm_gpio_request(dev, + ts->config_info.rst_gpio_number, NULL); + if (err) { + dev_err(dev, "Unable to request GPIO %d\n", + ts->config_info.rst_gpio_number); + return err; + } + } + + /* Check I2C Functionality */ + err = i2c_check_functionality(client->adapter, I2C_FUNC_I2C); + if (!err) { + dev_err(dev, "Check I2C Functionality Failed.\n"); + return -ENODEV; + } + + err = devm_request_threaded_irq(dev, client->irq, + NULL, vtl_ts_irq, + IRQF_ONESHOT, + client->name, ts); + if (err) { + dev_err(&client->dev, "VTL Failed to register interrupt\n"); + + goto ERR_IRQ_REQ; + } + + vtl_ts_hw_reset(); + + /*init input dev*/ + err = init_input_dev(ts); + if (err) { + + dev_err(dev, "init input dev failed.\n"); + goto ERR_INIT_INPUT; + } + + /* register early suspend */ +#ifdef CONFIG_HAS_EARLYSUSPEND + ts->driver->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + ts->driver->early_suspend.suspend = vtl_ts_early_suspend; + ts->driver->early_suspend.resume = vtl_ts_early_resume; + register_early_suspend(&ts->driver->early_suspend); +#endif + /* Create work queue */ + INIT_WORK(&ts->driver->event_work, vtl_ts_workfunc); + ts->driver->workqueue = create_singlethread_workqueue(DRIVER_NAME); + + return 0; + +ERR_IRQ_REQ: + cancel_work_sync(&ts->driver->event_work); + destroy_workqueue(ts->driver->workqueue); + +ERR_INIT_INPUT: + input_free_device(ts->driver->input_dev); + gpio_free(ts->config_info.rst_gpio_number); +ERR_TS_CONFIG: + + return err; +} + + +static SIMPLE_DEV_PM_OPS(vtl_ts_pm_ops, vtl_ts_suspend, vtl_ts_resume); + +static const struct of_device_id vtl_ts_dt_ids[] = { + { .compatible = "vtl,ct365", }, + { } +}; +MODULE_DEVICE_TABLE(of, vtl_ts_dt_ids); + + +static struct i2c_driver vtl_ts_driver = { + .probe = vtl_ts_probe, + .remove = vtl_ts_remove, + .id_table = vtl_ts_id, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .pm = &vtl_ts_pm_ops, + .of_match_table = of_match_ptr(vtl_ts_dt_ids), + }, +}; + +module_i2c_driver(vtl_ts_driver); + +MODULE_AUTHOR("VTL"); +MODULE_DESCRIPTION("VTL TouchScreen driver"); +MODULE_LICENSE("GPL"); |