diff options
author | Troy Kisky <troy.kisky@boundarydevices.com> | 2012-07-03 17:04:57 -0700 |
---|---|---|
committer | Troy Kisky <troy.kisky@boundarydevices.com> | 2012-07-03 17:04:57 -0700 |
commit | 2f0c84b3619deaaebeb3daa0166bed54bfdac190 (patch) | |
tree | 121ecf51a8d628f9f9af642f859dcf8956a2d508 /drivers/input/touchscreen/ft5x06_ts.c | |
parent | bfecaa2c2dc7583a42523c2ab136d21a97887289 (diff) | |
parent | e06baef465e2ee37e286b264182d4a23f8f2c067 (diff) |
Merge branch 'boundary-L3.0.15_12.04.01' of github.com:boundarydevices/linux-imx6 into boundary-L3.0.15_12.04.013.0-boundary-imx6-201207031704
Diffstat (limited to 'drivers/input/touchscreen/ft5x06_ts.c')
-rw-r--r-- | drivers/input/touchscreen/ft5x06_ts.c | 550 |
1 files changed, 550 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/ft5x06_ts.c b/drivers/input/touchscreen/ft5x06_ts.c new file mode 100644 index 000000000000..24cfe3fab381 --- /dev/null +++ b/drivers/input/touchscreen/ft5x06_ts.c @@ -0,0 +1,550 @@ +/* + * Boundary Devices FTx06 touch screen controller. + * + * Copyright (c) by Boundary Devices <info@boundarydevices.com> + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include <mach/gpio.h> +#include <linux/proc_fs.h> +#include <linux/delay.h> +#include <linux/input.h> + +#ifdef CONFIG_TOUCHSCREEN_FT5X06_SINGLE_TOUCH +#else +#define USE_ABS_MT +#endif + +struct point { + int x; + int y; +}; + +struct ft5x06_ts { + struct i2c_client *client; + struct input_dev *idev; + wait_queue_head_t sample_waitq; + struct semaphore sem; + struct completion init_exit; + struct task_struct *rtask; + int use_count; + int bReady; + int irq; + unsigned gp; + struct proc_dir_entry *procentry; +}; +static const char *client_name = "ft5x06"; + +struct ft5x06_ts *gts; + +static char const procentryname[] = { + "ft5x06" +}; + +static int ts_startup(struct ft5x06_ts *ts); +static void ts_shutdown(struct ft5x06_ts *ts); + +static int ft5x06_proc_read + (char *page, + char **start, + off_t off, + int count, + int *eof, + void *data) +{ + printk(KERN_ERR "%s\n", __func__); + return 0 ; +} + +static int +ft5x06_proc_write + (struct file *file, + const char __user *buffer, + unsigned long count, + void *data) +{ + printk(KERN_ERR "%s\n", __func__); + return count ; +} + +/*-----------------------------------------------------------------------*/ +static inline void ts_evt_add(struct ft5x06_ts *ts, + unsigned buttons, struct point *p) +{ + struct input_dev *idev = ts->idev; + int i; + if (!buttons) { + /* send release to user space. */ +#ifdef USE_ABS_MT + input_event(idev, EV_ABS, ABS_MT_TOUCH_MAJOR, 0); + input_event(idev, EV_KEY, BTN_TOUCH, 0); + input_mt_sync(idev); +#else + input_report_abs(idev, ABS_PRESSURE, 0); + input_report_key(idev, BTN_TOUCH, 0); + input_sync(idev); +#endif + } else { + for (i = 0; i < buttons; i++) { +#ifdef USE_ABS_MT + input_event(idev, EV_ABS, ABS_MT_POSITION_X, p[i].x); + input_event(idev, EV_ABS, ABS_MT_POSITION_Y, p[i].y); + input_event(idev, EV_ABS, ABS_MT_TOUCH_MAJOR, 1); + input_mt_sync(idev); +#else + input_report_abs(idev, ABS_X, p[i].x); + input_report_abs(idev, ABS_Y, p[i].y); + input_report_abs(idev, ABS_PRESSURE, 1); + input_report_key(idev, BTN_TOUCH, 1); + input_sync(idev); +#endif + } + input_event(idev, EV_KEY, BTN_TOUCH, 1); + } +#ifdef USE_ABS_MT + input_sync(idev); +#endif +} + +static int ts_open(struct input_dev *idev) +{ + struct ft5x06_ts *ts = input_get_drvdata(idev); + return ts_startup(ts); +} + +static void ts_close(struct input_dev *idev) +{ + struct ft5x06_ts *ts = input_get_drvdata(idev); + ts_shutdown(ts); +} + +static inline int ts_register(struct ft5x06_ts *ts) +{ + struct input_dev *idev; + idev = input_allocate_device(); + if (idev == NULL) + return -ENOMEM; + + ts->idev = idev; + idev->name = procentryname ; + idev->id.product = ts->client->addr; + idev->open = ts_open; + idev->close = ts_close; + + __set_bit(EV_ABS, idev->evbit); + __set_bit(EV_KEY, idev->evbit); + __set_bit(BTN_TOUCH, idev->keybit); + +#ifdef USE_ABS_MT + input_set_abs_params(idev, ABS_MT_POSITION_X, 0, 1023, 0, 0); + input_set_abs_params(idev, ABS_MT_POSITION_Y, 0, 0x255, 0, 0); + input_set_abs_params(idev, ABS_MT_TOUCH_MAJOR, 0, 1, 0, 0); +#else + __set_bit(EV_SYN, idev->evbit); + input_set_abs_params(idev, ABS_X, 0, 1023, 0, 0); + input_set_abs_params(idev, ABS_Y, 0, 0x255, 0, 0); + input_set_abs_params(idev, ABS_PRESSURE, 0, 1, 0, 0); +#endif + + input_set_drvdata(idev, ts); + return input_register_device(idev); +} + +static inline void ts_deregister(struct ft5x06_ts *ts) +{ + if (ts->idev) { + input_unregister_device(ts->idev); + input_free_device(ts->idev); + ts->idev = NULL; + } +} + +#ifdef DEBUG +static void printHex(u8 const *buf, unsigned len) +{ + char hex[512]; + char *next = hex ; + char *end = hex+sizeof(hex); + + while (len--) { + next += snprintf(next, end-next, "%02x", *buf++); + if (next >= end) { + hex[sizeof(hex)-1] = '\0' ; + break; + } + } + printk(KERN_ERR "%s\n", hex); +} +#endif + +static void write_reg(struct ft5x06_ts *ts, int regnum, int value) +{ + u8 regnval[] = { + regnum, + value + }; + struct i2c_msg pkt = { + ts->client->addr, 0, sizeof(regnval), regnval + }; + int ret = i2c_transfer(ts->client->adapter, &pkt, 1); + if (ret != 1) + printk(KERN_WARNING "%s: i2c_transfer failed\n", __func__); + else + printk(KERN_DEBUG "%s: set register 0x%02x to 0x%02x\n", + __func__, regnum, value); +} + +static void set_mode(struct ft5x06_ts *ts, int mode) +{ + write_reg(ts, 0, (mode&7)<<4); + printk(KERN_DEBUG "%s: changed mode to 0x%02x\n", __func__, mode); +} + +#define WORK_MODE 0 +#define FACTORY_MODE 4 + +/*-----------------------------------------------------------------------*/ + +/* + * This is a RT kernel thread that handles the I2c accesses + * The I2c access functions are expected to be able to sleep. + */ +static int ts_thread(void *_ts) +{ + int ret; + struct point points[5]; + unsigned char buf[33]; + struct ft5x06_ts *ts = _ts; + unsigned char startch[1] = { 0 }; + struct i2c_msg readpkt[2] = { + {ts->client->addr, 0, 1, startch}, + {ts->client->addr, I2C_M_RD, sizeof(buf), buf} + }; + + struct task_struct *tsk = current; + + ts->rtask = tsk; + + daemonize("ft5x06tsd"); + /* only want to receive SIGKILL */ + allow_signal(SIGKILL); + + complete(&ts->init_exit); + + do { + int buttons = 0 ; + ts->bReady = 0; + ret = i2c_transfer(ts->client->adapter, readpkt, + ARRAY_SIZE(readpkt)); + if (ret != ARRAY_SIZE(readpkt)) { + printk(KERN_WARNING "%s: i2c_transfer failed\n", + client_name); + msleep(1000); + } else { + int i; + unsigned char *p = buf+3; +#ifdef DEBUG + printHex(buf, sizeof(buf)); +#endif + buttons = buf[2]; + if (buttons > 5) { + printk(KERN_ERR + "%s: invalid button count %02x\n", + __func__, buttons); + buttons = 0 ; + } else { + for (i = 0; i < buttons; i++) { + points[i].x = ((p[0] << 8) + | p[1]) & 0x7ff; + points[i].y = ((p[2] << 8) + | p[3]) & 0x7ff; + p += 6; + } + } + } + + if (signal_pending(tsk)) + break; +#ifdef DEBUG + printk(KERN_ERR "%s: buttons = %d, " + "points[0].x = %d, " + "points[0].y = %d\n", + client_name, buttons, points[0].x, points[0].y); +#endif + ts_evt_add(ts, buttons, points); + if (0 < buttons) + wait_event_interruptible_timeout(ts->sample_waitq, + ts->bReady, HZ/20); + else + wait_event_interruptible(ts->sample_waitq, ts->bReady); + if (gpio_get_value(ts->gp)) { + if (buttons) { + buttons = 0; + ts_evt_add(ts, buttons, points); + } + if (signal_pending(tsk)) + break; + } + } while (1); + + ts->rtask = NULL; + complete_and_exit(&ts->init_exit, 0); +} + +/* + * We only detect samples ready with this interrupt + * handler, and even then we just schedule our task. + */ +static irqreturn_t ts_interrupt(int irq, void *id) +{ + struct ft5x06_ts *ts = id; + int bit = gpio_get_value(ts->gp); + if (bit == 0) { + ts->bReady = 1; + wmb(); /* flush bReady */ + wake_up(&ts->sample_waitq); + } + return IRQ_HANDLED; +} + +#define ID_G_THGROUP 0x80 +#define ID_G_PERIODMONITOR 0x89 +#define FT5X0X_REG_HEIGHT_B 0x8a +#define FT5X0X_REG_MAX_FRAME 0x8b +#define FT5X0X_REG_FEG_FRAME 0x8e +#define FT5X0X_REG_LEFT_RIGHT_OFFSET 0x92 +#define FT5X0X_REG_UP_DOWN_OFFSET 0x93 +#define FT5X0X_REG_DISTANCE_LEFT_RIGHT 0x94 +#define FT5X0X_REG_DISTANCE_UP_DOWN 0x95 +#define FT5X0X_REG_MAX_X_HIGH 0x98 +#define FT5X0X_REG_MAX_X_LOW 0x99 +#define FT5X0X_REG_MAX_Y_HIGH 0x9a +#define FT5X0X_REG_MAX_Y_LOW 0x9b +#define FT5X0X_REG_K_X_HIGH 0x9c +#define FT5X0X_REG_K_X_LOW 0x9d +#define FT5X0X_REG_K_Y_HIGH 0x9e +#define FT5X0X_REG_K_Y_LOW 0x9f + +#define ID_G_AUTO_CLB 0xa0 +#define ID_G_B_AREA_TH 0xae + +#ifdef DEBUG +static void dumpRegs(struct ft5x06_ts *ts, unsigned start, unsigned end) +{ + u8 regbuf[512]; + unsigned char startch[1] = { start }; + int ret ; + struct i2c_msg readpkt[2] = { + {ts->client->addr, 0, 1, startch}, + {ts->client->addr, I2C_M_RD, end-start+1, regbuf} + }; + ret = i2c_transfer(ts->client->adapter, readpkt, ARRAY_SIZE(readpkt)); + if (ret != ARRAY_SIZE(readpkt)) { + printk(KERN_WARNING "%s: i2c_transfer failed\n", client_name); + } else { + printk(KERN_ERR "registers %02x..%02x\n", start, end); + printHex(regbuf, end-start+1); + } +} +#endif + +static int ts_startup(struct ft5x06_ts *ts) +{ + int ret = 0; + if (ts == NULL) + return -EIO; + + if (down_interruptible(&ts->sem)) + return -EINTR; + + if (ts->use_count++ != 0) + goto out; + + if (ts->rtask) + panic("ft5x06tsd: rtask running?"); + + ret = request_irq(ts->irq, &ts_interrupt, IRQF_TRIGGER_FALLING, + client_name, ts); + if (ret) { + printk(KERN_ERR "%s: request_irq failed, irq:%i\n", + client_name, ts->irq); + goto out; + } + +#ifdef DEBUG + set_mode(ts, FACTORY_MODE); + dumpRegs(ts, 0x4c, 0x4C); + write_reg(ts, 0x4C, 0x05); + dumpRegs(ts, 0, 0x4C); +#endif + set_mode(ts, WORK_MODE); +#ifdef DEBUG + dumpRegs(ts, 0x3b, 0x3b); + dumpRegs(ts, 0x6a, 0x6a); + dumpRegs(ts, ID_G_THGROUP, ID_G_PERIODMONITOR); + dumpRegs(ts, FT5X0X_REG_HEIGHT_B, FT5X0X_REG_K_Y_LOW); + dumpRegs(ts, ID_G_AUTO_CLB, ID_G_B_AREA_TH); +#endif + set_mode(ts, WORK_MODE); + + init_completion(&ts->init_exit); + ret = kernel_thread(ts_thread, ts, CLONE_KERNEL); + if (ret >= 0) { + wait_for_completion(&ts->init_exit); + ret = 0; + } else { + free_irq(ts->irq, ts); + } + + out: + if (ret) + ts->use_count--; + up(&ts->sem); + return ret; +} + +/* + * Release touchscreen resources. Disable IRQs. + */ +static void ts_shutdown(struct ft5x06_ts *ts) +{ + if (ts) { + down(&ts->sem); + if (--ts->use_count == 0) { + if (ts->rtask) { + send_sig(SIGKILL, ts->rtask, 1); + wait_for_completion(&ts->init_exit); + } + free_irq(ts->irq, ts); + } + up(&ts->sem); + } +} +/*-----------------------------------------------------------------------*/ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int ts_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + return -ENODEV; + strlcpy(info->type, "ft5x06-ts", I2C_NAME_SIZE); + return 0; +} + +static int ts_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int err = 0; + struct ft5x06_ts *ts; + struct device *dev = &client->dev; + if (gts) { + printk(KERN_ERR "%s: Error gts is already allocated\n", + client_name); + return -ENOMEM; + } + ts = kzalloc(sizeof(struct ft5x06_ts), GFP_KERNEL); + if (!ts) { + dev_err(dev, "Couldn't allocate memory for %s\n", client_name); + return -ENOMEM; + } + init_waitqueue_head(&ts->sample_waitq); + sema_init(&ts->sem, 1); + ts->client = client; + ts->irq = client->irq ; + ts->gp = irq_to_gpio(client->irq); + printk(KERN_INFO "%s: %s touchscreen irq=%i, gp=%i\n", __func__, + client_name, ts->irq, ts->gp); + i2c_set_clientdata(client, ts); + err = ts_register(ts); + if (err == 0) { + gts = ts; + ts->procentry = create_proc_entry(procentryname, 0, NULL); + if (ts->procentry) { + ts->procentry->read_proc = ft5x06_proc_read ; + ts->procentry->write_proc = ft5x06_proc_write ; + } + } else { + printk(KERN_WARNING "%s: ts_register failed\n", client_name); + ts_deregister(ts); + kfree(ts); + } + return err; +} + +static int ts_remove(struct i2c_client *client) +{ + struct ft5x06_ts *ts = i2c_get_clientdata(client); + remove_proc_entry(procentryname, 0); + if (ts == gts) { + gts = NULL; + ts_deregister(ts); + } else { + printk(KERN_ERR "%s: Error ts!=gts\n", client_name); + } + kfree(ts); + return 0; +} + + +/*-----------------------------------------------------------------------*/ + +static const struct i2c_device_id ts_idtable[] = { + { "ft5x06-ts", 0 }, + { } +}; + +static struct i2c_driver ts_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ft5x06-ts", + }, + .id_table = ts_idtable, + .probe = ts_probe, + .remove = __devexit_p(ts_remove), + .detect = ts_detect, +}; + +static int __init ts_init(void) +{ + int res = i2c_add_driver(&ts_driver); + if (res) { + printk(KERN_WARNING "%s: i2c_add_driver failed\n", client_name); + return res; + } + printk(KERN_INFO "%s: " __DATE__ "\n", client_name); + return 0; +} + +static void __exit ts_exit(void) +{ + i2c_del_driver(&ts_driver); +} + +MODULE_AUTHOR("Boundary Devices <info@boundarydevices.com>"); +MODULE_DESCRIPTION("I2C interface for FocalTech ft5x06 touch screen controller."); +MODULE_LICENSE("GPL"); + +module_init(ts_init) +module_exit(ts_exit) |