aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/input/touchscreen/pcap7200_ts.c289
1 files changed, 289 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/pcap7200_ts.c b/drivers/input/touchscreen/pcap7200_ts.c
new file mode 100644
index 00000000000..4c35caf9d15
--- /dev/null
+++ b/drivers/input/touchscreen/pcap7200_ts.c
@@ -0,0 +1,289 @@
+ /* Projected capacitive touchscreen controller driver.
+ *
+ * Copyright(c) 2008 Openmoko Inc.
+ *
+ * Author: Matt Hsu <matt_hsu@openmoko.org>
+ *
+ * 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.
+ *
+ * TODO
+ * - apply ts_filter
+ * - add support for gesture event
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/input.h>
+
+#include <linux/pcap7200.h>
+#include <linux/ts_filter.h>
+
+#include <mach/om-gta03.h>
+
+#define PCAP7200_OP_MODE_REG 0x07
+#define RPT_PKT_SIZE 5
+#define EVENT_UP 0x80
+#define EVENT_DOWN 0x81
+
+
+#define coord_interpret(msb_byte, lsb_byte) \
+ (msb_byte << 7 | lsb_byte)
+
+struct pcap7200_data{
+ struct i2c_client *client;
+ struct input_dev *dev;
+ struct ts_filter *tsf[MAX_TS_FILTER_CHAIN];
+ struct mutex lock;
+ int irq;
+ struct work_struct work;
+};
+
+static void pcap7200_work(struct work_struct *work)
+{
+ struct pcap7200_data *pcap =
+ container_of(work, struct pcap7200_data, work);
+ uint8_t rpt_pkt[RPT_PKT_SIZE], event;
+ int coords[2];
+ int ret;
+
+ mutex_lock(&pcap->lock);
+
+ memset(rpt_pkt, 0, sizeof(rpt_pkt));
+
+ BUG_ON(pcap == NULL);
+
+ ret = i2c_master_recv(pcap->client, rpt_pkt, RPT_PKT_SIZE);
+
+ event = rpt_pkt[0];
+
+ /*
+ * this is annonying. system would receive two consecutive interrupts if we set INT
+ * type as LOW_LEVEL. the data we pull from pcap7200 are invalid which are all
+ * zero in the second interrupt.
+ */
+ if (event != 0) {
+ dev_dbg(&pcap->client->dev, "[%2x][%2x][%2x][%2x][%2x]: %d bytes return \n",
+ rpt_pkt[0], rpt_pkt[1], rpt_pkt[2],
+ rpt_pkt[3], rpt_pkt[4], ret);
+
+ coords[0] = coord_interpret(rpt_pkt[1], rpt_pkt[2]);
+ coords[1] = coord_interpret(rpt_pkt[3], rpt_pkt[4]);
+
+ if (event == EVENT_DOWN) {
+ input_report_abs(pcap->dev, ABS_X, coords[0]);
+ input_report_abs(pcap->dev, ABS_Y, coords[1]);
+ input_report_key(pcap->dev, BTN_TOUCH, 1);
+ input_report_abs(pcap->dev, ABS_PRESSURE, 1);
+ } else if (event == EVENT_UP) {
+ input_report_key(pcap->dev, BTN_TOUCH, 0);
+ input_report_abs(pcap->dev, ABS_PRESSURE, 0);
+ } else {
+ /* FIMXE: gesture events should be reported here. */
+ }
+
+ input_sync(pcap->dev);
+ }
+
+ mutex_unlock(&pcap->lock);
+ enable_irq(pcap->irq);
+}
+
+static const char *op_mode_name[] = {
+ [SLEEP] = "sleep",
+ [WAKEUP] = "wakeup",
+ [SINGLE_TOUCH] = "single_touch",
+ [MULTI_TOUCH] = "multi_touch",
+};
+
+static struct i2c_driver pcap7200_driver;
+
+static int __set_op_mode(struct pcap7200_data *pcap, u_int8_t val)
+{
+ u8 buf[] = { PCAP7200_OP_MODE_REG, val};
+ int ret, i;
+
+ mutex_lock(&pcap->lock);
+
+ /* this chip has an issue.
+ * you need to give wakeup call for 3 times if it's
+ * in sleep mode.
+ */
+ if (val == WAKEUP) {
+ for (i = 0; i < 3; i++)
+ ret = i2c_master_send(pcap->client, &buf, sizeof(buf));
+ } else
+ ret = i2c_master_send(pcap->client, &buf, sizeof(buf));
+
+ mutex_unlock(&pcap->lock);
+ return ret;
+}
+
+static ssize_t set_op_mode(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct pcap7200_data *pcap = i2c_get_clientdata(client);
+ u_int8_t i;
+
+ for (i = 0; i < ARRAY_SIZE(op_mode_name); i++) {
+ if (!strncmp(buf, op_mode_name[i], strlen(op_mode_name[i])))
+ __set_op_mode(pcap, i);
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(op_mode, S_IRUGO | S_IWUSR, NULL, set_op_mode);
+
+static irqreturn_t pcap7200_irq(int irq, void *_pcap)
+{
+ struct pcap7200_data *pcap = _pcap;
+
+ disable_irq(pcap->irq);
+ schedule_work(&pcap->work);
+
+ return IRQ_HANDLED;
+}
+
+static int
+pcap7200_probe(struct i2c_client *client, const struct i2c_device_id *ids)
+{
+ struct pcap7200_data *pcap;
+ struct input_dev *input_dev;
+ int err;
+
+ /* allocat pcap7200 data */
+ pcap = kzalloc(sizeof(struct pcap7200_data), GFP_KERNEL);
+ if (!pcap)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, pcap);
+ pcap->client = client;
+
+ mutex_init(&pcap->lock);
+
+ /* initialize input device */
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ dev_err(&client->dev, "Unable to allocate the input device\n");
+ err = -ENOMEM;
+ goto exit_kfree;
+ }
+
+ pcap->dev = input_dev;
+ input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) |
+ BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+ /* configurable resolution 2048x2048 */
+ input_set_abs_params(pcap->dev, ABS_X, 0, 0x7FF, 0, 0);
+ input_set_abs_params(pcap->dev, ABS_Y, 0, 0x7FF, 0, 0);
+ input_set_abs_params(pcap->dev, ABS_PRESSURE, 0, 1, 0, 0);
+
+ input_dev->name = client->name;
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->dev.parent = &client->dev;
+
+ err = input_register_device(input_dev);
+
+ if (err)
+ goto exit_unreg;
+
+ INIT_WORK(&pcap->work, pcap7200_work);
+
+ sysfs_create_file(&client->dev.kobj, &dev_attr_op_mode.attr);
+
+ /* setup IRQ */
+ if (client->irq < 0) {
+ dev_err(&client->dev,
+ "No irq allocated in client resources!\n");
+ return -EIO;
+ }
+
+ pcap->irq = client->irq;
+ err = request_irq(pcap->irq, pcap7200_irq, IRQF_TRIGGER_LOW, "pcap7200", pcap);
+
+ if (err < 0)
+ goto exit_unreg;
+
+ return 0;
+
+exit_unreg:
+ input_unregister_device(input_dev);
+exit_kfree:
+ kfree(pcap);
+ return err;
+}
+
+static int pcap7200_remove(struct i2c_client *client)
+{
+ struct pcap7200_data *pcap = i2c_get_clientdata(client);
+
+ free_irq(pcap->irq, pcap);
+ input_unregister_device(pcap->dev);
+ kfree(pcap);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int pcap7200_suspend(struct device *dev, pm_message_t state)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct pcap7200_data *pcap = i2c_get_clientdata(client);
+
+ __set_op_mode(pcap, SLEEP);
+ return 0;
+}
+
+static int pcap7200_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct pcap7200_data *pcap = i2c_get_clientdata(client);
+
+ __set_op_mode(pcap, WAKEUP);
+ return 0;
+}
+#else
+#define pcap7200_suspend NULL
+#define pcap7200_resume NULL
+#endif
+
+static struct i2c_device_id pcap7200_id_table[] = {
+ {"pcap7200", 0x0a},
+};
+
+static struct i2c_driver pcap7200_driver = {
+ .driver = {
+ .name = "pcap7200",
+ .suspend = pcap7200_suspend,
+ .resume = pcap7200_resume,
+ },
+ .id_table = pcap7200_id_table,
+ .probe = pcap7200_probe,
+ .remove = pcap7200_remove,
+};
+
+static int __init pcap7200_init(void)
+{
+ return i2c_add_driver(&pcap7200_driver);
+}
+
+static void pcap7200_exit(void)
+{
+ i2c_del_driver(&pcap7200_driver);
+}
+module_init(pcap7200_init);
+module_exit(pcap7200_exit);
+
+MODULE_AUTHOR("Matt Hsu <matt_hsu@openmoko.org>");
+MODULE_LICENSE("GPLv2");