MLK-10142-1 touchscreen: elan: add elan touch screen driver support
Add elan touch screen driver in linux kernel. Signed-off-by: Haibo Chen <haibo.chen@freescale.com> (cherry picked from commit 20180719c54760359d78544d55e4ac1fabaf6e8b) (Vipul: Fixed merge conflicts) TODO: checkpatch warnings Signed-off-by: Vipul Kumar <vipul_kumar@mentor.com>5.4-rM2-2.2.x-imx-squashed
parent
901b3581cd
commit
b96148101f
|
@ -363,6 +363,18 @@ config TOUCHSCREEN_EXC3000
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called exc3000.
|
||||
|
||||
config TOUCHSCREEN_ELAN_TS
|
||||
tristate "ELAN touchscreen input driver"
|
||||
depends on I2C
|
||||
help
|
||||
Say Y here if you have an I2C ELAN touchscreen
|
||||
attached.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called elan-touch.
|
||||
|
||||
config TOUCHSCREEN_FUJITSU
|
||||
tristate "Fujitsu serial touchscreen"
|
||||
select SERIO
|
||||
|
|
|
@ -39,6 +39,7 @@ obj-$(CONFIG_TOUCHSCREEN_EETI) += eeti_ts.o
|
|||
obj-$(CONFIG_TOUCHSCREEN_EKTF2127) += ektf2127.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_ELAN) += elants_i2c.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_ELO) += elo.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_ELAN_TS) += elan_ts.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_EGALAX) += egalax_ts.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_EGALAX_SERIAL) += egalax_ts_serial.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_EXC3000) += exc3000.o
|
||||
|
|
|
@ -0,0 +1,472 @@
|
|||
/*
|
||||
* Copyright (C) 2007-2008 HTC Corporation.
|
||||
*
|
||||
* Copyright (C) 2013-2015 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This driver is adapted from elan8232_i2c.c written by Shan-Fu Chiou
|
||||
* <sfchiou@gmail.com> and Jay Tu <jay_tu@htc.com>.
|
||||
* This driver is also adapted from the ELAN Touch Screen driver
|
||||
* written by Stanley Zeng <stanley.zeng@emc.com.tw>
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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/input.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/gpio.h>
|
||||
|
||||
static const char ELAN_TS_NAME[] = "elan-touch";
|
||||
|
||||
#define ELAN_TS_X_MAX 1088
|
||||
#define ELAN_TS_Y_MAX 768
|
||||
#define ELAN_USER_X_MAX 800
|
||||
#define ELAN_USER_Y_MAX 600
|
||||
#define IDX_PACKET_SIZE 8
|
||||
|
||||
enum {
|
||||
hello_packet = 0x55,
|
||||
idx_coordinate_packet = 0x5a,
|
||||
};
|
||||
|
||||
enum {
|
||||
idx_finger_state = 7,
|
||||
};
|
||||
|
||||
static struct workqueue_struct *elan_wq;
|
||||
|
||||
static struct elan_data {
|
||||
int intr_gpio;
|
||||
int use_irq;
|
||||
struct hrtimer timer;
|
||||
struct work_struct work;
|
||||
struct i2c_client *client;
|
||||
struct input_dev *input;
|
||||
wait_queue_head_t wait;
|
||||
} elan_touch_data;
|
||||
|
||||
/*--------------------------------------------------------------*/
|
||||
static int elan_touch_detect_int_level(void)
|
||||
{
|
||||
int v;
|
||||
v = gpio_get_value(elan_touch_data.intr_gpio);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
static int __elan_touch_poll(struct i2c_client *client)
|
||||
{
|
||||
int status = 0, retry = 20;
|
||||
|
||||
do {
|
||||
status = elan_touch_detect_int_level();
|
||||
retry--;
|
||||
mdelay(20);
|
||||
} while (status == 1 && retry > 0);
|
||||
|
||||
return status == 0 ? 0 : -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int elan_touch_poll(struct i2c_client *client)
|
||||
{
|
||||
return __elan_touch_poll(client);
|
||||
}
|
||||
|
||||
static int __hello_packet_handler(struct i2c_client *client)
|
||||
{
|
||||
int rc;
|
||||
uint8_t buf_recv[4] = { 0 };
|
||||
|
||||
rc = elan_touch_poll(client);
|
||||
|
||||
if (rc < 0)
|
||||
return -EINVAL;
|
||||
|
||||
rc = i2c_master_recv(client, buf_recv, 4);
|
||||
|
||||
if (rc != 4) {
|
||||
return rc;
|
||||
} else {
|
||||
int i;
|
||||
pr_info("hello packet: [0x%02x 0x%02x 0x%02x 0x%02x]\n",
|
||||
buf_recv[0], buf_recv[1], buf_recv[2], buf_recv[3]);
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
if (buf_recv[i] != hello_packet)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int elan_touch_parse_xy(uint8_t *data, uint16_t *x,
|
||||
uint16_t *y)
|
||||
{
|
||||
*x = (data[0] & 0xf0);
|
||||
*x <<= 4;
|
||||
*x |= data[1];
|
||||
if (*x >= ELAN_TS_X_MAX)
|
||||
*x = ELAN_TS_X_MAX;
|
||||
*x = ((((ELAN_TS_X_MAX -
|
||||
*x) * 1000) / ELAN_TS_X_MAX) * ELAN_USER_X_MAX) / 1000;
|
||||
|
||||
*y = (data[0] & 0x0f);
|
||||
*y <<= 8;
|
||||
*y |= data[2];
|
||||
if (*y >= ELAN_TS_Y_MAX)
|
||||
*y = ELAN_TS_Y_MAX;
|
||||
*y = ((((ELAN_TS_Y_MAX -
|
||||
*y) * 1000) / ELAN_TS_Y_MAX) * ELAN_USER_Y_MAX) / 1000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* __elan_touch_init -- hand shaking with touch panel
|
||||
*
|
||||
* 1.recv hello packet
|
||||
*/
|
||||
static int __elan_touch_init(struct i2c_client *client)
|
||||
{
|
||||
int rc;
|
||||
rc = __hello_packet_handler(client);
|
||||
if (rc < 0)
|
||||
goto hand_shake_failed;
|
||||
|
||||
hand_shake_failed:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int elan_touch_recv_data(struct i2c_client *client, uint8_t *buf)
|
||||
{
|
||||
int rc, bytes_to_recv = IDX_PACKET_SIZE;
|
||||
|
||||
if (buf == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
memset(buf, 0, bytes_to_recv);
|
||||
rc = i2c_master_recv(client, buf, bytes_to_recv);
|
||||
if (rc != bytes_to_recv)
|
||||
return -EINVAL;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void elan_touch_report_data(struct i2c_client *client, uint8_t *buf)
|
||||
{
|
||||
switch (buf[0]) {
|
||||
case idx_coordinate_packet:
|
||||
{
|
||||
uint16_t x1, x2, y1, y2;
|
||||
uint8_t finger_stat;
|
||||
|
||||
finger_stat = (buf[idx_finger_state] & 0x06) >> 1;
|
||||
|
||||
if (finger_stat == 0) {
|
||||
input_report_key(elan_touch_data.input, BTN_TOUCH, 0);
|
||||
input_report_key(elan_touch_data.input, BTN_2, 0);
|
||||
} else if (finger_stat == 1) {
|
||||
elan_touch_parse_xy(&buf[1], &x1, &y1);
|
||||
input_report_abs(elan_touch_data.input, ABS_X, x1);
|
||||
input_report_abs(elan_touch_data.input, ABS_Y, y1);
|
||||
input_report_key(elan_touch_data.input, BTN_TOUCH, 1);
|
||||
input_report_key(elan_touch_data.input, BTN_2, 0);
|
||||
} else if (finger_stat == 2) {
|
||||
elan_touch_parse_xy(&buf[1], &x1, &y1);
|
||||
input_report_abs(elan_touch_data.input, ABS_X, x1);
|
||||
input_report_abs(elan_touch_data.input, ABS_Y, y1);
|
||||
input_report_key(elan_touch_data.input, BTN_TOUCH, 1);
|
||||
elan_touch_parse_xy(&buf[4], &x2, &y2);
|
||||
input_report_abs(elan_touch_data.input, ABS_HAT0X, x2);
|
||||
input_report_abs(elan_touch_data.input, ABS_HAT0Y, y2);
|
||||
input_report_key(elan_touch_data.input, BTN_2, 1);
|
||||
}
|
||||
input_sync(elan_touch_data.input);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void elan_touch_work_func(struct work_struct *work)
|
||||
{
|
||||
int rc;
|
||||
uint8_t buf[IDX_PACKET_SIZE] = { 0 };
|
||||
struct i2c_client *client = elan_touch_data.client;
|
||||
|
||||
if (elan_touch_detect_int_level())
|
||||
return;
|
||||
|
||||
rc = elan_touch_recv_data(client, buf);
|
||||
if (rc < 0)
|
||||
return;
|
||||
|
||||
elan_touch_report_data(client, buf);
|
||||
}
|
||||
|
||||
static irqreturn_t elan_touch_ts_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
queue_work(elan_wq, &elan_touch_data.work);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static enum hrtimer_restart elan_touch_timer_func(struct hrtimer *timer)
|
||||
{
|
||||
queue_work(elan_wq, &elan_touch_data.work);
|
||||
hrtimer_start(&elan_touch_data.timer, ktime_set(0, 12500000),
|
||||
HRTIMER_MODE_REL);
|
||||
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
static int elan_touch_register_interrupt(struct i2c_client *client)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
if (client->irq) {
|
||||
elan_touch_data.use_irq = 1;
|
||||
err =
|
||||
request_irq(client->irq, elan_touch_ts_interrupt,
|
||||
IRQF_TRIGGER_FALLING, ELAN_TS_NAME,
|
||||
&elan_touch_data);
|
||||
|
||||
if (err < 0) {
|
||||
pr_info("%s(%s): Can't allocate irq %d\n", __FILE__,
|
||||
__func__, client->irq);
|
||||
elan_touch_data.use_irq = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!elan_touch_data.use_irq) {
|
||||
hrtimer_init(&elan_touch_data.timer, CLOCK_MONOTONIC,
|
||||
HRTIMER_MODE_REL);
|
||||
elan_touch_data.timer.function = elan_touch_timer_func;
|
||||
hrtimer_start(&elan_touch_data.timer, ktime_set(1, 0),
|
||||
HRTIMER_MODE_REL);
|
||||
}
|
||||
|
||||
pr_info("elan ts starts in %s mode.\n",
|
||||
elan_touch_data.use_irq == 1 ? "interrupt" : "polling");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int elan_touch_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device_node *np = client->dev.of_node;
|
||||
int gpio_elan_cs, gpio_elan_rst, err = 0;
|
||||
|
||||
if (!np)
|
||||
return -ENODEV;
|
||||
|
||||
elan_touch_data.intr_gpio = of_get_named_gpio(np, "gpio_intr", 0);
|
||||
if (!gpio_is_valid(elan_touch_data.intr_gpio))
|
||||
return -ENODEV;
|
||||
|
||||
err = devm_gpio_request_one(&client->dev, elan_touch_data.intr_gpio,
|
||||
GPIOF_IN, "gpio_elan_intr");
|
||||
if (err < 0) {
|
||||
dev_err(&client->dev,
|
||||
"request gpio failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* elan touch init */
|
||||
gpio_elan_cs = of_get_named_gpio(np, "gpio_elan_cs", 0);
|
||||
if (!gpio_is_valid(gpio_elan_cs))
|
||||
return -ENODEV;
|
||||
|
||||
err = devm_gpio_request_one(&client->dev, gpio_elan_cs,
|
||||
GPIOF_OUT_INIT_HIGH, "gpio_elan_cs");
|
||||
if (err < 0) {
|
||||
dev_err(&client->dev,
|
||||
"request gpio failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
gpio_set_value(gpio_elan_cs, 0);
|
||||
|
||||
gpio_elan_rst = of_get_named_gpio(np, "gpio_elan_rst", 0);
|
||||
if (!gpio_is_valid(gpio_elan_rst))
|
||||
return -ENODEV;
|
||||
|
||||
err = devm_gpio_request_one(&client->dev, gpio_elan_rst,
|
||||
GPIOF_OUT_INIT_HIGH, "gpio_elan_rst");
|
||||
if (err < 0) {
|
||||
dev_err(&client->dev,
|
||||
"request gpio failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
gpio_set_value(gpio_elan_rst, 0);
|
||||
msleep(10);
|
||||
gpio_set_value(gpio_elan_rst, 1);
|
||||
|
||||
gpio_set_value(gpio_elan_cs, 1);
|
||||
msleep(100);
|
||||
|
||||
elan_wq = create_singlethread_workqueue("elan_wq");
|
||||
if (!elan_wq) {
|
||||
err = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
elan_touch_data.client = client;
|
||||
strlcpy(client->name, ELAN_TS_NAME, I2C_NAME_SIZE);
|
||||
|
||||
INIT_WORK(&elan_touch_data.work, elan_touch_work_func);
|
||||
|
||||
elan_touch_data.input = input_allocate_device();
|
||||
if (elan_touch_data.input == NULL) {
|
||||
err = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = __elan_touch_init(client);
|
||||
if (err < 0) {
|
||||
dev_err(&client->dev, "elan - Read Hello Packet Failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
elan_touch_data.input->name = ELAN_TS_NAME;
|
||||
elan_touch_data.input->id.bustype = BUS_I2C;
|
||||
|
||||
set_bit(EV_SYN, elan_touch_data.input->evbit);
|
||||
|
||||
set_bit(EV_KEY, elan_touch_data.input->evbit);
|
||||
set_bit(BTN_TOUCH, elan_touch_data.input->keybit);
|
||||
set_bit(BTN_2, elan_touch_data.input->keybit);
|
||||
|
||||
set_bit(EV_ABS, elan_touch_data.input->evbit);
|
||||
set_bit(ABS_X, elan_touch_data.input->absbit);
|
||||
set_bit(ABS_Y, elan_touch_data.input->absbit);
|
||||
set_bit(ABS_HAT0X, elan_touch_data.input->absbit);
|
||||
set_bit(ABS_HAT0Y, elan_touch_data.input->absbit);
|
||||
|
||||
input_set_abs_params(elan_touch_data.input, ABS_X, 0, ELAN_USER_X_MAX,
|
||||
0, 0);
|
||||
input_set_abs_params(elan_touch_data.input, ABS_Y, 0, ELAN_USER_Y_MAX,
|
||||
0, 0);
|
||||
input_set_abs_params(elan_touch_data.input, ABS_HAT0X, 0,
|
||||
ELAN_USER_X_MAX, 0, 0);
|
||||
input_set_abs_params(elan_touch_data.input, ABS_HAT0Y, 0,
|
||||
ELAN_USER_Y_MAX, 0, 0);
|
||||
|
||||
err = input_register_device(elan_touch_data.input);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
|
||||
elan_touch_register_interrupt(elan_touch_data.client);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
input_free_device(elan_touch_data.input);
|
||||
if (elan_wq)
|
||||
destroy_workqueue(elan_wq);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int elan_touch_remove(struct i2c_client *client)
|
||||
{
|
||||
if (elan_wq)
|
||||
destroy_workqueue(elan_wq);
|
||||
|
||||
input_unregister_device(elan_touch_data.input);
|
||||
|
||||
if (elan_touch_data.use_irq)
|
||||
free_irq(client->irq, client);
|
||||
else
|
||||
hrtimer_cancel(&elan_touch_data.timer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
static const struct i2c_device_id elan_touch_id[] = {
|
||||
{"elan-touch", 0},
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct of_device_id elan_dt_ids[] = {
|
||||
{
|
||||
.compatible = "elan,elan-touch",
|
||||
}, {
|
||||
/* sentinel */
|
||||
}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, elan_dt_ids);
|
||||
|
||||
static int elan_suspend(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int elan_resume(struct device *dev)
|
||||
{
|
||||
uint8_t buf[IDX_PACKET_SIZE] = { 0 };
|
||||
|
||||
if (0 == elan_touch_detect_int_level()) {
|
||||
dev_dbg(dev, "Got touch during suspend period.\n");
|
||||
/*
|
||||
* if touch screen during suspend, recv and drop the
|
||||
* data, then touch interrupt pin will return high after
|
||||
* receving data.
|
||||
*/
|
||||
elan_touch_recv_data(elan_touch_data.client, buf);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops elan_dev_pm_ops = {
|
||||
.suspend = elan_suspend,
|
||||
.resume = elan_resume,
|
||||
};
|
||||
|
||||
static struct i2c_driver elan_touch_driver = {
|
||||
.probe = elan_touch_probe,
|
||||
.remove = elan_touch_remove,
|
||||
.id_table = elan_touch_id,
|
||||
.driver = {
|
||||
.name = "elan-touch",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = elan_dt_ids,
|
||||
#ifdef CONFIG_PM
|
||||
.pm = &elan_dev_pm_ops,
|
||||
#endif
|
||||
},
|
||||
};
|
||||
|
||||
static int __init elan_touch_init(void)
|
||||
{
|
||||
return i2c_add_driver(&elan_touch_driver);
|
||||
}
|
||||
|
||||
static void __exit elan_touch_exit(void)
|
||||
{
|
||||
i2c_del_driver(&elan_touch_driver);
|
||||
}
|
||||
|
||||
module_init(elan_touch_init);
|
||||
module_exit(elan_touch_exit);
|
||||
|
||||
MODULE_AUTHOR("Stanley Zeng <stanley.zeng@emc.com.tw>");
|
||||
MODULE_DESCRIPTION("ELAN Touch Screen driver");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Reference in New Issue