diff options
Diffstat (limited to 'packages/linux/linux-2.6.21/tsc2003.c')
-rw-r--r-- | packages/linux/linux-2.6.21/tsc2003.c | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/packages/linux/linux-2.6.21/tsc2003.c b/packages/linux/linux-2.6.21/tsc2003.c new file mode 100644 index 0000000000..8d7e5b3f78 --- /dev/null +++ b/packages/linux/linux-2.6.21/tsc2003.c @@ -0,0 +1,557 @@ +/* + * linux/drivers/i2c/chips/tsc2003.c + * + * Copyright (C) 2005 Bill Gatliff <bgat at billgatliff.com> + * + * 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. + * + * Driver for TI's TSC2003 I2C Touch Screen Controller + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/string.h> +#include <linux/bcd.h> +#include <linux/list.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/input.h> +#include <linux/platform_device.h> +#include <asm/delay.h> +#include <linux/delay.h> + +#include <asm/arch/gpio.h> + +#define DRIVER_NAME "tsc2003" + +enum tsc2003_pd { + PD_POWERDOWN = 0, /* penirq */ + PD_IREFOFF_ADCON = 1, /* no penirq */ + PD_IREFON_ADCOFF = 2, /* penirq */ + PD_IREFON_ADCON = 3, /* no penirq */ + PD_PENIRQ_ARM = PD_IREFON_ADCOFF, + PD_PENIRQ_DISARM = PD_IREFON_ADCON, +}; + +enum tsc2003_m { + M_12BIT = 0, + M_8BIT = 1 +}; + +enum tsc2003_cmd { + MEAS_TEMP0 = 0, + MEAS_VBAT1 = 1, + MEAS_IN1 = 2, + MEAS_TEMP1 = 4, + MEAS_VBAT2 = 5, + MEAS_IN2 = 6, + ACTIVATE_NX_DRIVERS = 8, + ACTIVATE_NY_DRIVERS = 9, + ACTIVATE_YNX_DRIVERS = 10, + MEAS_XPOS = 12, + MEAS_YPOS = 13, + MEAS_Z1POS = 14, + MEAS_Z2POS = 15 +}; + +#define TSC2003_CMD(cn,pdn,m) (((cn) << 4) | ((pdn) << 2) | ((m) << 1)) + +#define ADC_MAX ((1 << 12) - 1) + +struct tsc2003_data { + struct i2c_client *client; + struct input_dev *idev; + struct timer_list penirq_timer; + struct semaphore sem; + struct task_struct *tstask; + struct completion tstask_completion; + enum tsc2003_pd pd; + enum tsc2003_m m; + int vbat1; + int vbat2; + int temp0; + int temp1; + int in1; + int in2; +}; + +static int tsc2003_i2c_detect (struct i2c_adapter *adapter, int address, + int kind); + +static inline int tsc2003_command (struct tsc2003_data *data, + enum tsc2003_cmd cmd, + enum tsc2003_pd pd) +{ + char c; + int ret; + down(&data->sem); + c = TSC2003_CMD(cmd, pd, data->m); + ret = i2c_master_send(data->client, &c, 1); + up(&data->sem); + return ret; +} + +static int tsc2003_read (struct tsc2003_data *data, + enum tsc2003_cmd cmd, + enum tsc2003_pd pd, + int *val) +{ + char c; + char d[2]; + int ret; + + c = TSC2003_CMD(cmd, pd, data->m); + ret = i2c_master_send(data->client, &c, 1); + + udelay(20); + ret = i2c_master_recv(data->client, d, data->m == M_12BIT ? 2 : 1); + + if (val) + { + *val = d[0]; + *val <<= 4; + if (data->m == M_12BIT) + *val += (d[1] >> 4); + } + +#if defined(CONFIG_I2C_DEBUG_CHIP) + printk(KERN_ERR "%s: val[%x] = %d\n", + __FUNCTION__, cmd, (((int)d[0]) << 8) + d[1]); +#endif + + return 0; + if (!ret) ret = -ENODEV; + return ret; +} + +static inline int tsc2003_read_temp0 (struct tsc2003_data *d, enum + tsc2003_pd pd, int *t) +{ + return tsc2003_read(d, MEAS_TEMP0, pd, t); +} + +static inline int tsc2003_read_temp1 (struct tsc2003_data *d, enum + tsc2003_pd pd, int *t) +{ + return tsc2003_read(d, MEAS_TEMP1, pd, t); +} + +static inline int tsc2003_read_xpos (struct tsc2003_data *d, enum + tsc2003_pd pd, int *x) +{ + return tsc2003_read(d, MEAS_XPOS, pd, x); +} + +static inline int tsc2003_read_ypos (struct tsc2003_data *d, enum + tsc2003_pd pd, int *y) +{ + return tsc2003_read(d, MEAS_YPOS, pd, y); +} + +static inline int tsc2003_read_pressure (struct tsc2003_data *d, enum + tsc2003_pd pd, int *p) +{ + return tsc2003_read(d, MEAS_Z1POS, pd, p); +} + +static inline int tsc2003_read_in1 (struct tsc2003_data *d, enum + tsc2003_pd pd, int *t) +{ + return tsc2003_read(d, MEAS_IN1, pd, t); +} + +static inline int tsc2003_read_in2 (struct tsc2003_data *d, enum + tsc2003_pd pd, int *t) +{ + return tsc2003_read(d, MEAS_IN2, pd, t); +} + +static inline int tsc2003_read_vbat1 (struct tsc2003_data *d, enum + tsc2003_pd pd, int *t) +{ + return tsc2003_read(d, MEAS_VBAT1, pd, t); +} + +static inline int tsc2003_read_vbat2 (struct tsc2003_data *d, enum + tsc2003_pd pd, int *t) +{ + return tsc2003_read(d, MEAS_VBAT2, pd, t); +} + +static inline int tsc2003_powerdown (struct tsc2003_data *d) +{ + /* we don't have a distinct powerdown command, + so do a benign read with the PD bits cleared */ + return tsc2003_read(d, MEAS_IN1, PD_POWERDOWN, 0); +} + +void tsc2003_init_client (struct i2c_client *client) +{ + struct tsc2003_data *data = i2c_get_clientdata(client); + + data->pd = PD_PENIRQ_DISARM; + data->m = M_8BIT; + return; +} + +static int tsc2003ts_thread (void *v) +{ + struct tsc2003_data *d = v; + struct task_struct *tsk = current; + int pendown=0; + + d->tstask = tsk; + + daemonize(DRIVER_NAME "tsd"); + allow_signal(SIGKILL); + + complete(&d->tstask_completion); + + printk(KERN_INFO "%s: address 0x%x\n", + __FUNCTION__, d->client->addr); + + while (!signal_pending(tsk)) + { + unsigned int x, y, p; + + tsc2003_read_xpos(d, PD_PENIRQ_DISARM, &x); + tsc2003_read_ypos(d, PD_PENIRQ_DISARM, &y); + tsc2003_read_pressure(d, PD_PENIRQ_DISARM, &p); + + /* non-X-Y driver read to avoid glitch in penirq (errata?) */ + if (p > 100) { + pendown = 1; + input_report_abs(d->idev, ABS_X, x); + input_report_abs(d->idev, ABS_Y, y); + input_report_abs(d->idev, ABS_PRESSURE, p); + input_report_key(d->idev, BTN_TOUCH, 1); + input_sync(d->idev); + } else if (pendown == 1) { + pendown = 0; + input_report_key(d->idev, BTN_TOUCH, 0); + input_report_abs(d->idev, ABS_PRESSURE, 0); + input_sync(d->idev); + } + schedule_timeout_interruptible(msecs_to_jiffies(10)); + } + + d->tstask = NULL; + complete_and_exit(&d->tstask_completion, 0); +} + +static int tsc2003_idev_open (struct input_dev *idev) +{ + struct tsc2003_data *d = idev->private; + int ret = 0; + + if (down_interruptible(&d->sem)) + return -EINTR; + + if (d->tstask) + panic(DRIVER_NAME "tsd already running (!). abort."); + + init_completion(&d->tstask_completion); + + ret = kernel_thread(tsc2003ts_thread, d, CLONE_KERNEL); + if (ret >= 0) { + wait_for_completion(&d->tstask_completion); + ret = 0; + } + + up(&d->sem); + + return ret; +} + +static void tsc2003_idev_close (struct input_dev *idev) +{ + struct tsc2003_data *d = idev->private; + down_interruptible(&d->sem); + if (d->tstask) + { + send_sig(SIGKILL, d->tstask, 1); + wait_for_completion(&d->tstask_completion); + } + up(&d->sem); + return; +} + +#if defined(CONFIG_SYSFS) && defined(CONFIG_SENSORS_TSC2003_SYSFS) +static ssize_t show_addr (struct device *dev, char *buf) +{ + struct tsc2003_data *d = container_of(dev->driver, struct + tsc2003_data, driver); + return sprintf(buf, "%d\n", d->client.addr); +} +static DEVICE_ATTR(addr, S_IRUGO, show_addr, NULL); + +static ssize_t show_vbat1 (struct device *dev, char *buf) +{ + struct tsc2003_data *d = container_of(dev->driver, struct + tsc2003_data, driver); + return sprintf(buf, "%d\n", d->vbat1); +} +static DEVICE_ATTR(vbat1, S_IRUGO, show_vbat1, NULL); + +static ssize_t show_vbat2 (struct device *dev, char *buf) +{ + struct tsc2003_data *d = container_of(dev->driver, struct + tsc2003_data, driver); + return sprintf(buf, "%d\n", d->vbat2); +} +static DEVICE_ATTR(vbat2, S_IRUGO, show_vbat2, NULL); + +static ssize_t show_in1 (struct device *dev, char *buf) +{ + struct tsc2003_data *d = container_of(dev->driver, struct + tsc2003_data, driver); + return sprintf(buf, "%d\n", d->in1); +} +static DEVICE_ATTR(in1, S_IRUGO, show_in1, NULL); + +static ssize_t show_in2 (struct device *dev, char *buf) +{ + struct tsc2003_data *d = container_of(dev->driver, struct + tsc2003_data, driver); + return sprintf(buf, "%d\n", d->in2); +} +static DEVICE_ATTR(in2, S_IRUGO, show_in2, NULL); + +static ssize_t show_temp0 (struct device *dev, char *buf) +{ + struct tsc2003_data *d = container_of(dev->driver, struct + tsc2003_data, driver); + return sprintf(buf, "%d\n", d->temp0); +} +static DEVICE_ATTR(temp0, S_IRUGO, show_temp0, NULL); + +static ssize_t show_temp1 (struct device *dev, char *buf) +{ + struct tsc2003_data *d = container_of(dev->driver, struct + tsc2003_data, driver); + return sprintf(buf, "%d\n", d->temp1); +} +static DEVICE_ATTR(temp1, S_IRUGO, show_temp1, NULL); + +#warning "TODO: this daemon sometimes hangs the touchscreen daemon" +#warning "TODO: under periods of heavy touch screen activity." +#warning "TODO: Use with caution until the bug is squashed." +static int tsc2003s_thread (void *v) +{ + struct tsc2003_data *d = v; + + daemonize(DRIVER_NAME "sd"); + allow_signal(SIGKILL); + + printk(KERN_INFO "%s: address 0x%x\n", + __FUNCTION__, d->client.addr); + + while (!signal_pending(current)) + { + if (!down_interruptible(&d->sem)) + { + if (!timer_pending(&d->penirq_timer)) + { + tsc2003_read_vbat1(d, d->pd, &d->vbat1); + tsc2003_read_vbat2(d, d->pd, &d->vbat2); + tsc2003_read_in1(d, d->pd, &d->in1); + tsc2003_read_in2(d, d->pd, &d->in2); + tsc2003_read_temp0(d, d->pd, &d->temp0); + tsc2003_read_temp1(d, d->pd, &d->temp1); + } + up(&d->sem); + } + set_task_state(current, TASK_INTERRUPTIBLE); + schedule_timeout(5 * HZ); + } + do_exit(0); +} +#endif + +static int tsc2003_driver_register (struct tsc2003_data *data) +{ + int ret = 0; + + init_MUTEX(&data->sem); + + data->idev = input_allocate_device(); + if(!data->idev) + { + return -ENOMEM; + } + data->idev->private = data; + data->idev->name = DRIVER_NAME; + data->idev->evbit[0] = BIT(EV_ABS); + data->idev->open = tsc2003_idev_open; + data->idev->close = tsc2003_idev_close; + data->idev->absbit[LONG(ABS_X)] = BIT(ABS_X); + data->idev->absbit[LONG(ABS_Y)] = BIT(ABS_Y); + data->idev->absbit[LONG(ABS_PRESSURE)] = BIT(ABS_PRESSURE); + input_set_abs_params(data->idev, ABS_X, 0, ADC_MAX, 0, 0); + input_set_abs_params(data->idev, ABS_Y, 0, ADC_MAX, 0, 0); + + ret = input_register_device(data->idev); + if(ret) + { + input_free_device(data->idev); + return ret; + } + return ret; +} + +/* Magic definition of all other variables and things */ +static unsigned short normal_i2c[] = {0x48, 0x49, 0x4a, 0x48b, I2C_CLIENT_END }; + +I2C_CLIENT_INSMOD; + +static int tsc2003_i2c_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_probe(adapter, &addr_data, tsc2003_i2c_detect); +} + +static int tsc2003_i2c_detach_client(struct i2c_client *client) +{ + struct tsc2003_data *data=i2c_get_clientdata(client); + int err; + + input_unregister_device(data->idev); + + err = i2c_detach_client(client); + if (err) { + dev_err(&client->dev, "Client deregistration failed, " + "client not detached.\n"); + return err; + } + + return 0; +} + +static struct i2c_driver tsc2003_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, + .attach_adapter = tsc2003_i2c_attach_adapter, + .detach_client = tsc2003_i2c_detach_client, + .command = NULL +}; + +static struct i2c_client client_template = { + .name = "TSC2003", + .driver = &tsc2003_i2c_driver, +}; + +static int tsc2003_i2c_detect (struct i2c_adapter *adap, int addr, + int kind) +{ + struct i2c_client *i2c; + int ret; + struct tsc2003_data *data; + + client_template.adapter = adap; + client_template.addr = addr; + + printk(KERN_INFO "tsc2003 i2c touch screen controller\n"); + printk(KERN_INFO "Bill Gatliff <bgat at billgatliff.com>\n"); + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL){ + return -ENOMEM; + } + + data = kcalloc(1, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto err; + } + data->client = i2c; + i2c_set_clientdata(i2c, data); + + ret = i2c_attach_client(i2c); + + if (ret < 0) { + printk("failed to attach codec at addr %x\n", addr); + goto err; + } + + tsc2003_init_client(i2c); + + tsc2003_powerdown(data); + + ret = tsc2003_driver_register(data); + if(ret < 0) { + printk("driver_register failed\n"); + goto err; + } + + return ret; + +err: + kfree(i2c); + return ret; +} + +#define tsc2003_suspend NULL +#define tsc2003_resume NULL + +static int __devinit tsc2003_probe(struct platform_device *dev) +{ + int ret; + + ret=i2c_add_driver(&tsc2003_i2c_driver); + if(ret) + return ret; + +#if defined(CONFIG_SYSFS) && defined(CONFIG_SENSORS_TSC2003_SYSFS) + ret = kernel_thread(tsc2003s_thread, d, CLONE_KERNEL); + if (ret >= 0) + ret = 0; + + device_create_file(dev, &dev_attr_addr); + device_create_file(dev, &dev_attr_vbat1); + device_create_file(dev, &dev_attr_vbat2); + device_create_file(dev, &dev_attr_in1); + device_create_file(dev, &dev_attr_in2); + device_create_file(dev, &dev_attr_temp0); + device_create_file(dev, &dev_attr_temp1); +#endif + return 0; +} + +static int __devexit tsc2003_remove(struct platform_device *dev) +{ + i2c_del_driver(&tsc2003_i2c_driver); + return 0; +} + +static struct platform_driver tsc2003_driver = { + .probe = tsc2003_probe, + .remove = __devexit_p(tsc2003_remove), + .suspend = tsc2003_suspend, + .resume = tsc2003_resume, + .driver = { + .name = "tsc2003", + }, +}; + +static int __init tsc2003_init(void) +{ + return platform_driver_register(&tsc2003_driver); +} + +static void __exit tsc2003_exit(void) +{ + platform_driver_unregister(&tsc2003_driver); +} + +MODULE_AUTHOR("Bill Gatliff <bgat at billgatliff.com>"); +MODULE_DESCRIPTION("TSC2003 Touch Screen Controller driver"); +MODULE_LICENSE("GPL"); + +module_init(tsc2003_init); +module_exit(tsc2003_exit); |