/* * Copyright (C) 2007-2008 HTC Corporation. * * This driver is adapted from elan8232_i2c.c written by Shan-Fu Chiou * and Jay Tu . * This driver is also adapted from the ELAN Touch Screen driver * written by Stanley Zeng * * 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 #include #include #include #include #include #include #include #include #include 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) { unsigned 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; printk("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) { printk("%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); } printk("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) { int err = 0; 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.intr_gpio = irq_to_gpio(client->irq); pr_debug("irq_to_gpio irq %d, gpio %d\n", client->irq, elan_touch_data.intr_gpio); 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) { printk("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 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, }, }; 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 "); MODULE_DESCRIPTION("ELAN Touch Screen driver"); MODULE_LICENSE("GPL");