diff options
Diffstat (limited to 'drivers/input')
-rw-r--r-- | drivers/input/misc/Kconfig | 7 | ||||
-rw-r--r-- | drivers/input/misc/Makefile | 2 | ||||
-rw-r--r-- | drivers/input/misc/gp2a.c | 639 | ||||
-rw-r--r-- | drivers/input/touchscreen/Kconfig | 12 | ||||
-rw-r--r-- | drivers/input/touchscreen/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/touchscreen/atmel_mxt_ts.c | 4 | ||||
-rwxr-xr-x | drivers/input/touchscreen/mms_ts.c | 961 |
7 files changed, 1625 insertions, 1 deletions
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 6f4ad1a..1f8e6d5 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -494,4 +494,11 @@ config INPUT_XEN_KBDDEV_FRONTEND To compile this driver as a module, choose M here: the module will be called xen-kbdfront. +config OPTICAL_GP2A + depends on I2C && GENERIC_GPIO + tristate "GP2A ambient light and proximity input device" + default n + help + This option enables proximity & light sensors using gp2a driver. + endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index eb73834..059ce58 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -47,4 +47,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o obj-$(CONFIG_INPUT_YEALINK) += yealink.o - +obj-$(CONFIG_OPTICAL_GP2A) += gp2a.o
\ No newline at end of file diff --git a/drivers/input/misc/gp2a.c b/drivers/input/misc/gp2a.c new file mode 100644 index 0000000..472da90 --- /dev/null +++ b/drivers/input/misc/gp2a.c @@ -0,0 +1,639 @@ +/* linux/driver/input/misc/gp2a.c + * Copyright (C) 2010 Samsung Electronics. All rights reserved. + * + * 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/i2c.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/gpio.h> +#include <linux/wakelock.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/workqueue.h> +#include <linux/uaccess.h> +#include <linux/gp2a.h> + + +/* Note about power vs enable/disable: + * The chip has two functions, proximity and ambient light sensing. + * There is no separate power enablement to the two functions (unlike + * the Capella CM3602/3623). + * This module implements two drivers: /dev/proximity and /dev/light. + * When either driver is enabled (via sysfs attributes), we give power + * to the chip. When both are disabled, we remove power from the chip. + * In suspend, we remove power if light is disabled but not if proximity is + * enabled (proximity is allowed to wakeup from suspend). + * + * There are no ioctls for either driver interfaces. Output is via + * input device framework and control via sysfs attributes. + */ + + +#define gp2a_dbgmsg(str, args...) pr_debug("%s: " str, __func__, ##args) + +/* ADDSEL is LOW */ +#define REGS_PROX 0x0 /* Read Only */ +#define REGS_GAIN 0x1 /* Write Only */ +#define REGS_HYS 0x2 /* Write Only */ +#define REGS_CYCLE 0x3 /* Write Only */ +#define REGS_OPMOD 0x4 /* Write Only */ + +/* sensor type */ +#define LIGHT 0 +#define PROXIMITY 1 +#define ALL 2 + +#define DELAY_LOWBOUND (5 * NSEC_PER_MSEC) + +/* start time delay for light sensor in nano seconds */ +#define LIGHT_SENSOR_START_TIME_DELAY 50000000 + +static u8 reg_defaults[5] = { + 0x00, /* PROX: read only register */ + 0x08, /* GAIN: large LED drive level */ + 0xC2, /* HYS: receiver sensitivity */ + 0x04, /* CYCLE: */ + 0x01, /* OPMOD: normal operating mode */ +}; + +enum { + LIGHT_ENABLED = BIT(0), + PROXIMITY_ENABLED = BIT(1), +}; + +/* driver data */ +struct gp2a_data { + struct input_dev *proximity_input_dev; + struct input_dev *light_input_dev; + struct gp2a_platform_data *pdata; + struct i2c_client *i2c_client; + int irq; + struct work_struct work_light; + struct hrtimer timer; + ktime_t light_poll_delay; + bool on; + u8 power_state; + struct mutex power_lock; + struct wake_lock prx_wake_lock; + struct workqueue_struct *wq; +}; + +int gp2a_i2c_write(struct gp2a_data *gp2a, u8 reg, u8 *val) +{ + int err = 0; + struct i2c_msg msg[1]; + unsigned char data[2]; + int retry = 10; + struct i2c_client *client = gp2a->i2c_client; + + if ((client == NULL) || (!client->adapter)) + return -ENODEV; + + while (retry--) { + data[0] = reg; + data[1] = *val; + + msg->addr = client->addr; + msg->flags = 0; /* write */ + msg->len = 2; + msg->buf = data; + + err = i2c_transfer(client->adapter, msg, 1); + + if (err >= 0) + return 0; + } + return err; +} + +static void gp2a_light_enable(struct gp2a_data *gp2a) +{ + gp2a_dbgmsg("starting poll timer, delay %lldns\n", + ktime_to_ns(gp2a->light_poll_delay)); + /* + * Set far out of range ABS_MISC value, -1024, to enable real value to + * go through next. + */ + input_abs_set_val(gp2a->light_input_dev, ABS_MISC, -1024); + hrtimer_start(&gp2a->timer, ktime_set(0, LIGHT_SENSOR_START_TIME_DELAY), + HRTIMER_MODE_REL); +} + +static void gp2a_light_disable(struct gp2a_data *gp2a) +{ + gp2a_dbgmsg("cancelling poll timer\n"); + hrtimer_cancel(&gp2a->timer); + cancel_work_sync(&gp2a->work_light); +} + +static ssize_t poll_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + return sprintf(buf, "%lld\n", ktime_to_ns(gp2a->light_poll_delay)); +} + + +static ssize_t poll_delay_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + int64_t new_delay; + int err; + + err = strict_strtoll(buf, 10, &new_delay); + if (err < 0) + return err; + + gp2a_dbgmsg("new delay = %lldns, old delay = %lldns\n", + new_delay, ktime_to_ns(gp2a->light_poll_delay)); + + if (new_delay < DELAY_LOWBOUND) { + gp2a_dbgmsg("new delay less than low bound, so set delay " + "to %lld\n", (int64_t)DELAY_LOWBOUND); + new_delay = DELAY_LOWBOUND; + } + + mutex_lock(&gp2a->power_lock); + if (new_delay != ktime_to_ns(gp2a->light_poll_delay)) { + gp2a->light_poll_delay = ns_to_ktime(new_delay); + if (gp2a->power_state & LIGHT_ENABLED) { + gp2a_light_disable(gp2a); + gp2a_light_enable(gp2a); + } + } + mutex_unlock(&gp2a->power_lock); + + return size; +} + +static ssize_t light_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", + (gp2a->power_state & LIGHT_ENABLED) ? 1 : 0); +} + +static ssize_t proximity_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", + (gp2a->power_state & PROXIMITY_ENABLED) ? 1 : 0); +} + +static ssize_t light_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + bool new_value; + + if (sysfs_streq(buf, "1")) + new_value = true; + else if (sysfs_streq(buf, "0")) + new_value = false; + else { + pr_err("%s: invalid value %d\n", __func__, *buf); + return -EINVAL; + } + + mutex_lock(&gp2a->power_lock); + gp2a_dbgmsg("new_value = %d, old state = %d\n", + new_value, (gp2a->power_state & LIGHT_ENABLED) ? 1 : 0); + if (new_value && !(gp2a->power_state & LIGHT_ENABLED)) { + if (!gp2a->power_state) + gp2a->pdata->power(true); + gp2a->power_state |= LIGHT_ENABLED; + gp2a_light_enable(gp2a); + } else if (!new_value && (gp2a->power_state & LIGHT_ENABLED)) { + gp2a_light_disable(gp2a); + gp2a->power_state &= ~LIGHT_ENABLED; + if (!gp2a->power_state) + gp2a->pdata->power(false); + } + mutex_unlock(&gp2a->power_lock); + return size; +} + +static ssize_t proximity_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + bool new_value; + + if (sysfs_streq(buf, "1")) + new_value = true; + else if (sysfs_streq(buf, "0")) + new_value = false; + else { + pr_err("%s: invalid value %d\n", __func__, *buf); + return -EINVAL; + } + + mutex_lock(&gp2a->power_lock); + gp2a_dbgmsg("new_value = %d, old state = %d\n", + new_value, (gp2a->power_state & PROXIMITY_ENABLED) ? 1 : 0); + if (new_value && !(gp2a->power_state & PROXIMITY_ENABLED)) { + if (!gp2a->power_state) + gp2a->pdata->power(true); + gp2a->power_state |= PROXIMITY_ENABLED; + enable_irq(gp2a->irq); + enable_irq_wake(gp2a->irq); + gp2a_i2c_write(gp2a, REGS_GAIN, ®_defaults[1]); + gp2a_i2c_write(gp2a, REGS_HYS, ®_defaults[2]); + gp2a_i2c_write(gp2a, REGS_CYCLE, ®_defaults[3]); + gp2a_i2c_write(gp2a, REGS_OPMOD, ®_defaults[4]); + } else if (!new_value && (gp2a->power_state & PROXIMITY_ENABLED)) { + disable_irq_wake(gp2a->irq); + disable_irq(gp2a->irq); + gp2a_i2c_write(gp2a, REGS_OPMOD, ®_defaults[0]); + gp2a->power_state &= ~PROXIMITY_ENABLED; + if (!gp2a->power_state) + gp2a->pdata->power(false); + } + mutex_unlock(&gp2a->power_lock); + return size; +} + +static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP, + poll_delay_show, poll_delay_store); + +static struct device_attribute dev_attr_light_enable = + __ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP, + light_enable_show, light_enable_store); + +static struct device_attribute dev_attr_proximity_enable = + __ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP, + proximity_enable_show, proximity_enable_store); + +static struct attribute *light_sysfs_attrs[] = { + &dev_attr_light_enable.attr, + &dev_attr_poll_delay.attr, + NULL +}; + +static struct attribute_group light_attribute_group = { + .attrs = light_sysfs_attrs, +}; + +static struct attribute *proximity_sysfs_attrs[] = { + &dev_attr_proximity_enable.attr, + NULL +}; + +static struct attribute_group proximity_attribute_group = { + .attrs = proximity_sysfs_attrs, +}; + +static void gp2a_work_func_light(struct work_struct *work) +{ + struct gp2a_data *gp2a = container_of(work, struct gp2a_data, + work_light); + int adc = gp2a->pdata->light_adc_value(); + if (adc < 0) { + pr_err("adc returned error %d\n", adc); + return; + } + gp2a_dbgmsg("adc returned light value %d\n", adc); + input_report_abs(gp2a->light_input_dev, ABS_MISC, adc); + input_sync(gp2a->light_input_dev); +} + +/* This function is for light sensor. It operates every a few seconds. + * It asks for work to be done on a thread because i2c needs a thread + * context (slow and blocking) and then reschedules the timer to run again. + */ +static enum hrtimer_restart gp2a_timer_func(struct hrtimer *timer) +{ + struct gp2a_data *gp2a = container_of(timer, struct gp2a_data, timer); + queue_work(gp2a->wq, &gp2a->work_light); + hrtimer_forward_now(&gp2a->timer, gp2a->light_poll_delay); + return HRTIMER_RESTART; +} + +/* interrupt happened due to transition/change of near/far proximity state */ +irqreturn_t gp2a_irq_handler(int irq, void *data) +{ + struct gp2a_data *ip = data; + int val = gpio_get_value(ip->pdata->p_out); + if (val < 0) { + pr_err("%s: gpio_get_value error %d\n", __func__, val); + return IRQ_HANDLED; + } + + gp2a_dbgmsg("gp2a: proximity val=%d\n", val); + + /* 0 is close, 1 is far */ + input_report_abs(ip->proximity_input_dev, ABS_DISTANCE, val); + input_sync(ip->proximity_input_dev); + wake_lock_timeout(&ip->prx_wake_lock, 3*HZ); + return IRQ_HANDLED; +} + +static int gp2a_setup_irq(struct gp2a_data *gp2a) +{ + int rc = -EIO; + struct gp2a_platform_data *pdata = gp2a->pdata; + int irq; + + gp2a_dbgmsg("start\n"); + + rc = gpio_request(pdata->p_out, "gpio_proximity_out"); + if (rc < 0) { + pr_err("%s: gpio %d request failed (%d)\n", + __func__, pdata->p_out, rc); + return rc; + } + + rc = gpio_direction_input(pdata->p_out); + if (rc < 0) { + pr_err("%s: failed to set gpio %d as input (%d)\n", + __func__, pdata->p_out, rc); + goto err_gpio_direction_input; + } + + irq = gpio_to_irq(pdata->p_out); + rc = request_irq(irq, + gp2a_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "proximity_int", + gp2a); + if (rc < 0) { + pr_err("%s: request_irq(%d) failed for gpio %d (%d)\n", + __func__, irq, + pdata->p_out, rc); + goto err_request_irq; + } + + /* start with interrupts disabled */ + disable_irq(irq); + gp2a->irq = irq; + + /* sync input device with proximity gpio pin default value */ + gp2a_irq_handler(gp2a->irq, gp2a); + + gp2a_dbgmsg("success\n"); + + goto done; + +err_request_irq: +err_gpio_direction_input: + gpio_free(pdata->p_out); +done: + return rc; +} + +static int gp2a_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = -ENODEV; + struct input_dev *input_dev; + struct gp2a_data *gp2a; + struct gp2a_platform_data *pdata = client->dev.platform_data; + + if (!pdata) { + pr_err("%s: missing pdata!\n", __func__); + return ret; + } + if (!pdata->power || !pdata->light_adc_value) { + pr_err("%s: incomplete pdata!\n", __func__); + return ret; + } + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("%s: i2c functionality check failed!\n", __func__); + return ret; + } + + gp2a = kzalloc(sizeof(struct gp2a_data), GFP_KERNEL); + if (!gp2a) { + pr_err("%s: failed to alloc memory for module data\n", + __func__); + return -ENOMEM; + } + + gp2a->pdata = pdata; + gp2a->i2c_client = client; + i2c_set_clientdata(client, gp2a); + + + wake_lock_init(&gp2a->prx_wake_lock, WAKE_LOCK_SUSPEND, + "prx_wake_lock"); + mutex_init(&gp2a->power_lock); + + /* allocate proximity input_device */ + input_dev = input_allocate_device(); + if (!input_dev) { + pr_err("%s: could not allocate input device\n", __func__); + goto err_input_allocate_device_proximity; + } + gp2a->proximity_input_dev = input_dev; + input_set_drvdata(input_dev, gp2a); + input_dev->name = "proximity"; + input_set_capability(input_dev, EV_ABS, ABS_DISTANCE); + input_set_abs_params(input_dev, ABS_DISTANCE, 0, 1, 0, 0); + + ret = gp2a_setup_irq(gp2a); + if (ret) { + pr_err("%s: could not setup irq\n", __func__); + input_free_device(input_dev); + goto err_setup_irq; + } + + gp2a_dbgmsg("registering proximity input device\n"); + ret = input_register_device(input_dev); + if (ret < 0) { + pr_err("%s: could not register input device\n", __func__); + input_free_device(input_dev); + goto err_input_register_device_proximity; + } + ret = sysfs_create_group(&input_dev->dev.kobj, + &proximity_attribute_group); + if (ret) { + pr_err("%s: could not create sysfs group\n", __func__); + goto err_sysfs_create_group_proximity; + } + + /* hrtimer settings. we poll for light values using a timer. */ + hrtimer_init(&gp2a->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + gp2a->light_poll_delay = ns_to_ktime(200 * NSEC_PER_MSEC); + gp2a->timer.function = gp2a_timer_func; + + /* the timer just fires off a work queue request. we need a thread + * to read the i2c (can be slow and blocking) + */ + gp2a->wq = create_singlethread_workqueue("gp2a_wq"); + if (!gp2a->wq) { + ret = -ENOMEM; + pr_err("%s: could not create workqueue\n", __func__); + goto err_create_workqueue; + } + /* this is the thread function we run on the work queue */ + INIT_WORK(&gp2a->work_light, gp2a_work_func_light); + + /* allocate lightsensor-level input_device */ + input_dev = input_allocate_device(); + if (!input_dev) { + pr_err("%s: could not allocate input device\n", __func__); + ret = -ENOMEM; + goto err_input_allocate_device_light; + } + input_set_drvdata(input_dev, gp2a); + input_dev->name = "lightsensor-level"; + input_set_capability(input_dev, EV_ABS, ABS_MISC); + input_set_abs_params(input_dev, ABS_MISC, 0, 1023, 8, 0); + + gp2a_dbgmsg("registering lightsensor-level input device\n"); + ret = input_register_device(input_dev); + if (ret < 0) { + pr_err("%s: could not register input device\n", __func__); + input_free_device(input_dev); + goto err_input_register_device_light; + } + gp2a->light_input_dev = input_dev; + ret = sysfs_create_group(&input_dev->dev.kobj, + &light_attribute_group); + if (ret) { + pr_err("%s: could not create sysfs group\n", __func__); + goto err_sysfs_create_group_light; + } + goto done; + + /* error, unwind it all */ +err_sysfs_create_group_light: + input_unregister_device(gp2a->light_input_dev); +err_input_register_device_light: +err_input_allocate_device_light: + destroy_workqueue(gp2a->wq); +err_create_workqueue: + sysfs_remove_group(&gp2a->proximity_input_dev->dev.kobj, + &proximity_attribute_group); +err_sysfs_create_group_proximity: + input_unregister_device(gp2a->proximity_input_dev); +err_input_register_device_proximity: + free_irq(gp2a->irq, gp2a); + gpio_free(gp2a->pdata->p_out); +err_setup_irq: +err_input_allocate_device_proximity: + mutex_destroy(&gp2a->power_lock); + wake_lock_destroy(&gp2a->prx_wake_lock); + kfree(gp2a); +done: + return ret; +} + +static int gp2a_suspend(struct device *dev) +{ + /* We disable power only if proximity is disabled. If proximity + * is enabled, we leave power on because proximity is allowed + * to wake up device. We remove power without changing + * gp2a->power_state because we use that state in resume + */ + struct i2c_client *client = to_i2c_client(dev); + struct gp2a_data *gp2a = i2c_get_clientdata(client); + if (gp2a->power_state & LIGHT_ENABLED) + gp2a_light_disable(gp2a); + if (gp2a->power_state == LIGHT_ENABLED) + gp2a->pdata->power(false); + return 0; +} + +static int gp2a_resume(struct device *dev) +{ + /* Turn power back on if we were before suspend. */ + struct i2c_client *client = to_i2c_client(dev); + struct gp2a_data *gp2a = i2c_get_clientdata(client); + if (gp2a->power_state == LIGHT_ENABLED) + gp2a->pdata->power(true); + if (gp2a->power_state & LIGHT_ENABLED) + gp2a_light_enable(gp2a); + return 0; +} + +static int gp2a_i2c_remove(struct i2c_client *client) +{ + struct gp2a_data *gp2a = i2c_get_clientdata(client); + sysfs_remove_group(&gp2a->light_input_dev->dev.kobj, + &light_attribute_group); + sysfs_remove_group(&gp2a->proximity_input_dev->dev.kobj, + &proximity_attribute_group); + free_irq(gp2a->irq, gp2a); + destroy_workqueue(gp2a->wq); + input_unregister_device(gp2a->light_input_dev); + input_unregister_device(gp2a->proximity_input_dev); + gpio_free(gp2a->pdata->p_out); + if (gp2a->power_state) { + gp2a->power_state = 0; + if (gp2a->power_state & LIGHT_ENABLED) + gp2a_light_disable(gp2a); + gp2a->pdata->power(false); + } + mutex_destroy(&gp2a->power_lock); + wake_lock_destroy(&gp2a->prx_wake_lock); + kfree(gp2a); + return 0; +} + +static const struct i2c_device_id gp2a_device_id[] = { + {"gp2a", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, gp2a_device_id); + +static const struct dev_pm_ops gp2a_pm_ops = { + .suspend = gp2a_suspend, + .resume = gp2a_resume +}; + +static struct i2c_driver gp2a_i2c_driver = { + .driver = { + .name = "gp2a", + .owner = THIS_MODULE, + .pm = &gp2a_pm_ops + }, + .probe = gp2a_i2c_probe, + .remove = gp2a_i2c_remove, + .id_table = gp2a_device_id, +}; + + +static int __init gp2a_init(void) +{ + return i2c_add_driver(&gp2a_i2c_driver); +} + +static void __exit gp2a_exit(void) +{ + i2c_del_driver(&gp2a_i2c_driver); +} + +module_init(gp2a_init); +module_exit(gp2a_exit); + +MODULE_AUTHOR("mjchen@sta.samsung.com"); +MODULE_DESCRIPTION("Optical Sensor driver for gp2ap002a00f"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 4104103..86bc7a7 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -272,6 +272,18 @@ config TOUCHSCREEN_MCS5000 To compile this driver as a module, choose M here: the module will be called mcs5000_ts. +config TOUCHSCREEN_MMS + tristate "MELFAS MMS-series touchscreen" + depends on I2C + help + Say Y here if you have a MELFAS MMS-series touchscreen controller + chip in your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mms_ts. + config TOUCHSCREEN_MTOUCH tristate "MicroTouch serial touchscreens" select SERIO diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 0738f19..ce1fbe2 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_TOUCHSCREEN_LPC32XX) += lpc32xx_ts.o obj-$(CONFIG_TOUCHSCREEN_MAX11801) += max11801_ts.o obj-$(CONFIG_TOUCHSCREEN_MC13783) += mc13783_ts.o obj-$(CONFIG_TOUCHSCREEN_MCS5000) += mcs5000_ts.o +obj-$(CONFIG_TOUCHSCREEN_MMS) += mms_ts.o obj-$(CONFIG_TOUCHSCREEN_MIGOR) += migor_ts.o obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c index 1e61387..d4e1515 100644 --- a/drivers/input/touchscreen/atmel_mxt_ts.c +++ b/drivers/input/touchscreen/atmel_mxt_ts.c @@ -829,6 +829,10 @@ static int mxt_initialize(struct mxt_data *data) MXT_COMMAND_RESET, 1); msleep(MXT_RESET_TIME); + error = mxt_make_highchg(data); + if (error) + return error; + /* Update matrix size at info struct */ error = mxt_read_reg(client, MXT_MATRIX_X_SIZE, &val); if (error) diff --git a/drivers/input/touchscreen/mms_ts.c b/drivers/input/touchscreen/mms_ts.c new file mode 100755 index 0000000..a6ec6eb --- /dev/null +++ b/drivers/input/touchscreen/mms_ts.c @@ -0,0 +1,961 @@ +/* + * mms_ts.c - Touchscreen driver for Melfas MMS-series touch controllers + * + * Copyright (C) 2011 Google Inc. + * Author: Dima Zavin <dima@android.com> + * Simon Wilson <simonwilson@google.com> + * + * ISP reflashing code based on original code from Melfas. + * + * 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. + * + */ + +//#define DEBUG +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/earlysuspend.h> +#include <linux/firmware.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +#include <linux/platform_data/mms_ts.h> + +#include <asm/unaligned.h> + +#define MAX_FINGERS 10 +#define MAX_WIDTH 30 +#define MAX_PRESSURE 255 + +/* Registers */ +#define MMS_MODE_CONTROL 0x01 +#define MMS_XYRES_HI 0x02 +#define MMS_XRES_LO 0x03 +#define MMS_YRES_LO 0x04 + +#define MMS_INPUT_EVENT_PKT_SZ 0x0F +#define MMS_INPUT_EVENT0 0x10 +#define FINGER_EVENT_SZ 6 + +#define MMS_TSP_REVISION 0xF0 +#define MMS_HW_REVISION 0xF1 +#define MMS_COMPAT_GROUP 0xF2 +#define MMS_FW_VERSION 0xF3 + +enum { + ISP_MODE_FLASH_ERASE = 0x59F3, + ISP_MODE_FLASH_WRITE = 0x62CD, + ISP_MODE_FLASH_READ = 0x6AC9, +}; + +/* each address addresses 4-byte words */ +#define ISP_MAX_FW_SIZE (0x1F00 * 4) +#define ISP_IC_INFO_ADDR 0x1F00 + +static bool mms_force_reflash = false; +module_param_named(force_reflash, mms_force_reflash, bool, S_IWUSR | S_IRUGO); + +static bool mms_flash_from_probe; +module_param_named(flash_from_probe, mms_flash_from_probe, bool, + S_IWUSR | S_IRUGO); + +static bool mms_die_on_flash_fail = true; +module_param_named(die_on_flash_fail, mms_die_on_flash_fail, bool, + S_IWUSR | S_IRUGO); + +struct mms_ts_info { + struct i2c_client *client; + struct input_dev *input_dev; + char phys[32]; + + int max_x; + int max_y; + + bool invert_x; + bool invert_y; + + int irq; + + struct mms_ts_platform_data *pdata; + + char *fw_name; + struct completion init_done; + struct early_suspend early_suspend; + + /* protects the enabled flag */ + struct mutex lock; + bool enabled; +}; + +struct mms_fw_image { + __le32 hdr_len; + __le32 data_len; + __le32 fw_ver; + __le32 hdr_ver; + u8 data[0]; +} __attribute__ ((packed)); + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mms_ts_early_suspend(struct early_suspend *h); +static void mms_ts_late_resume(struct early_suspend *h); +#endif + +static irqreturn_t mms_ts_interrupt(int irq, void *dev_id) +{ + struct mms_ts_info *info = dev_id; + struct i2c_client *client = info->client; + u8 buf[MAX_FINGERS*FINGER_EVENT_SZ] = { 0 }; + int ret; + int i; + int sz; + u8 reg = MMS_INPUT_EVENT0; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .buf = ®, + .len = 1, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .buf = buf, + }, + }; + + sz = i2c_smbus_read_byte_data(client, MMS_INPUT_EVENT_PKT_SZ); + if (sz < 0) { + dev_err(&client->dev, "%s bytes=%d\n", __func__, sz); + goto out; + } + dev_dbg(&client->dev, "bytes available: %d\n", sz); + BUG_ON(sz > MAX_FINGERS*FINGER_EVENT_SZ); + if (sz == 0) + goto out; + + msg[1].len = sz; + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(&client->dev, + "failed to read %d bytes of touch data (%d)\n", + sz, ret); + goto out; + } + +#if defined(VERBOSE_DEBUG) + print_hex_dump(KERN_DEBUG, "mms_ts raw: ", + DUMP_PREFIX_OFFSET, 32, 1, buf, sz, false); +#endif + for (i = 0; i < sz; i += FINGER_EVENT_SZ) { + u8 *tmp = &buf[i]; + int id = (tmp[0] & 0xf) - 1; + int x = tmp[2] | ((tmp[1] & 0xf) << 8); + int y = tmp[3] | (((tmp[1] >> 4) & 0xf) << 8); + + if (info->invert_x) { + x = info->max_x - x; + if (x < 0) + x = 0; + } + if (info->invert_y) { + y = info->max_y - y; + if (y < 0) + y = 0; + } + + if ((tmp[0] & 0x80) == 0) { + dev_dbg(&client->dev, "finger %d up\n", id); + input_mt_slot(info->input_dev, id); + input_mt_report_slot_state(info->input_dev, + MT_TOOL_FINGER, false); + continue; + } + + input_mt_slot(info->input_dev, id); + input_mt_report_slot_state(info->input_dev, + MT_TOOL_FINGER, true); + input_report_abs(info->input_dev, ABS_MT_TOUCH_MAJOR, tmp[4]); + input_report_abs(info->input_dev, ABS_MT_PRESSURE, tmp[5]); + input_report_abs(info->input_dev, ABS_MT_POSITION_X, x); + input_report_abs(info->input_dev, ABS_MT_POSITION_Y, y); + + dev_dbg(&client->dev, + "finger %d: x=%d y=%d p=%d w=%d\n", id, x, y, tmp[5], + tmp[4]); + } + + input_sync(info->input_dev); + +out: + return IRQ_HANDLED; +} + +static void hw_reboot(struct mms_ts_info *info, bool bootloader) +{ + gpio_direction_output(info->pdata->gpio_vdd_en, 0); + gpio_direction_output(info->pdata->gpio_sda, bootloader ? 0 : 1); + gpio_direction_output(info->pdata->gpio_scl, bootloader ? 0 : 1); + gpio_direction_output(info->pdata->gpio_resetb, 0); + msleep(30); + gpio_set_value(info->pdata->gpio_vdd_en, 1); + msleep(30); + + if (bootloader) { + gpio_set_value(info->pdata->gpio_scl, 0); + gpio_set_value(info->pdata->gpio_sda, 1); + } else { + gpio_set_value(info->pdata->gpio_resetb, 1); + gpio_direction_input(info->pdata->gpio_resetb); + gpio_direction_input(info->pdata->gpio_scl); + gpio_direction_input(info->pdata->gpio_sda); + } + msleep(40); +} + +static inline void hw_reboot_bootloader(struct mms_ts_info *info) +{ + hw_reboot(info, true); +} + +static inline void hw_reboot_normal(struct mms_ts_info *info) +{ + hw_reboot(info, false); +} + +static inline void mms_pwr_on_reset(struct mms_ts_info *info) +{ + struct i2c_adapter *adapter = to_i2c_adapter(info->client->dev.parent); + + if (!info->pdata->mux_fw_flash) { + dev_info(&info->client->dev, + "missing platform data, can't do power-on-reset\n"); + return; + } + + i2c_lock_adapter(adapter); + info->pdata->mux_fw_flash(true); + + gpio_direction_output(info->pdata->gpio_vdd_en, 0); + gpio_direction_output(info->pdata->gpio_sda, 1); + gpio_direction_output(info->pdata->gpio_scl, 1); + gpio_direction_output(info->pdata->gpio_resetb, 1); + msleep(50); + gpio_direction_output(info->pdata->gpio_vdd_en, 1); + msleep(50); + + info->pdata->mux_fw_flash(false); + i2c_unlock_adapter(adapter); + + /* TODO: Seems long enough for the firmware to boot. + * Find the right value */ + msleep(250); +} + +static void isp_toggle_clk(struct mms_ts_info *info, int start_lvl, int end_lvl, + int hold_us) +{ + gpio_set_value(info->pdata->gpio_scl, start_lvl); + udelay(hold_us); + gpio_set_value(info->pdata->gpio_scl, end_lvl); + udelay(hold_us); +} + +/* 1 <= cnt <= 32 bits to write */ +static void isp_send_bits(struct mms_ts_info *info, u32 data, int cnt) +{ + gpio_direction_output(info->pdata->gpio_resetb, 0); + gpio_direction_output(info->pdata->gpio_scl, 0); + gpio_direction_output(info->pdata->gpio_sda, 0); + + /* clock out the bits, msb first */ + while (cnt--) { + gpio_set_value(info->pdata->gpio_sda, (data >> cnt) & 1); + udelay(3); + isp_toggle_clk(info, 1, 0, 3); + } +} + +/* 1 <= cnt <= 32 bits to read */ +static u32 isp_recv_bits(struct mms_ts_info *info, int cnt) +{ + u32 data = 0; + + gpio_direction_output(info->pdata->gpio_resetb, 0); + gpio_direction_output(info->pdata->gpio_scl, 0); + gpio_set_value(info->pdata->gpio_sda, 0); + gpio_direction_input(info->pdata->gpio_sda); + + /* clock in the bits, msb first */ + while (cnt--) { + isp_toggle_clk(info, 0, 1, 1); + data = (data << 1) | (!!gpio_get_value(info->pdata->gpio_sda)); + } + + gpio_direction_output(info->pdata->gpio_sda, 0); + return data; +} + +static void isp_enter_mode(struct mms_ts_info *info, u32 mode) +{ + int cnt; + unsigned long flags; + + local_irq_save(flags); + gpio_direction_output(info->pdata->gpio_resetb, 0); + gpio_direction_output(info->pdata->gpio_scl, 0); + gpio_direction_output(info->pdata->gpio_sda, 1); + + mode &= 0xffff; + for (cnt = 15; cnt >= 0; cnt--) { + gpio_set_value(info->pdata->gpio_resetb, (mode >> cnt) & 1); + udelay(3); + isp_toggle_clk(info, 1, 0, 3); + } + + gpio_set_value(info->pdata->gpio_resetb, 0); + local_irq_restore(flags); +} + +static void isp_exit_mode(struct mms_ts_info *info) +{ + int i; + unsigned long flags; + + local_irq_save(flags); + gpio_direction_output(info->pdata->gpio_resetb, 0); + udelay(3); + + for (i = 0; i < 10; i++) + isp_toggle_clk(info, 1, 0, 3); + local_irq_restore(flags); +} + +static void flash_set_address(struct mms_ts_info *info, u16 addr) +{ + /* Only 13 bits of addr are valid. + * The addr is in bits 13:1 of cmd */ + isp_send_bits(info, (u32)(addr & 0x1fff) << 1, 18); +} + +static void flash_erase(struct mms_ts_info *info) +{ + isp_enter_mode(info, ISP_MODE_FLASH_ERASE); + + gpio_direction_output(info->pdata->gpio_resetb, 0); + gpio_direction_output(info->pdata->gpio_scl, 0); + gpio_direction_output(info->pdata->gpio_sda, 1); + + /* 4 clock cycles with different timings for the erase to + * get processed, clk is already 0 from above */ + udelay(7); + isp_toggle_clk(info, 1, 0, 3); + udelay(7); + isp_toggle_clk(info, 1, 0, 3); + usleep_range(25000, 35000); + isp_toggle_clk(info, 1, 0, 3); + usleep_range(150, 200); + isp_toggle_clk(info, 1, 0, 3); + + gpio_set_value(info->pdata->gpio_sda, 0); + + isp_exit_mode(info); +} + +static u32 flash_readl(struct mms_ts_info *info, u16 addr) +{ + int i; + u32 val; + unsigned long flags; + + local_irq_save(flags); + isp_enter_mode(info, ISP_MODE_FLASH_READ); + flash_set_address(info, addr); + + gpio_direction_output(info->pdata->gpio_scl, 0); + gpio_direction_output(info->pdata->gpio_sda, 0); + udelay(40); + + /* data load cycle */ + for (i = 0; i < 6; i++) + isp_toggle_clk(info, 1, 0, 10); + + val = isp_recv_bits(info, 32); + isp_exit_mode(info); + local_irq_restore(flags); + + return val; +} + +static void flash_writel(struct mms_ts_info *info, u16 addr, u32 val) +{ + unsigned long flags; + + local_irq_save(flags); + isp_enter_mode(info, ISP_MODE_FLASH_WRITE); + flash_set_address(info, addr); + isp_send_bits(info, val, 32); + + gpio_direction_output(info->pdata->gpio_sda, 1); + /* 6 clock cycles with different timings for the data to get written + * into flash */ + isp_toggle_clk(info, 0, 1, 3); + isp_toggle_clk(info, 0, 1, 3); + isp_toggle_clk(info, 0, 1, 6); + isp_toggle_clk(info, 0, 1, 12); + isp_toggle_clk(info, 0, 1, 3); + isp_toggle_clk(info, 0, 1, 3); + + isp_toggle_clk(info, 1, 0, 1); + + gpio_direction_output(info->pdata->gpio_sda, 0); + isp_exit_mode(info); + local_irq_restore(flags); + usleep_range(300, 400); +} + +static bool flash_is_erased(struct mms_ts_info *info) +{ + struct i2c_client *client = info->client; + u32 val; + u16 addr; + + for (addr = 0; addr < (ISP_MAX_FW_SIZE / 4); addr++) { + udelay(40); + val = flash_readl(info, addr); + + if (val != 0xffffffff) { + dev_dbg(&client->dev, + "addr 0x%x not erased: 0x%08x != 0xffffffff\n", + addr, val); + return false; + } + } + return true; +} + +static int fw_write_image(struct mms_ts_info *info, const u8 *data, size_t len) +{ + struct i2c_client *client = info->client; + u16 addr = 0; + + for (addr = 0; addr < (len / 4); addr++, data += 4) { + u32 val = get_unaligned_le32(data); + u32 verify_val; + int retries = 3; + + while (retries--) { + flash_writel(info, addr, val); + verify_val = flash_readl(info, addr); + if (val == verify_val) + break; + dev_err(&client->dev, + "mismatch @ addr 0x%x: 0x%x != 0x%x\n", + addr, verify_val, val); + hw_reboot_bootloader(info); + continue; + } + if (retries < 0) + return -ENXIO; + } + + return 0; +} + +static int fw_download(struct mms_ts_info *info, const u8 *data, size_t len) +{ + struct i2c_client *client = info->client; + u32 val; + int ret = 0; + + if (len % 4) { + dev_err(&client->dev, + "fw image size (%d) must be a multiple of 4 bytes\n", + len); + return -EINVAL; + } else if (len > ISP_MAX_FW_SIZE) { + dev_err(&client->dev, + "fw image is too big, %d > %d\n", len, ISP_MAX_FW_SIZE); + return -EINVAL; + } + + dev_info(&client->dev, "fw download start\n"); + + gpio_direction_output(info->pdata->gpio_vdd_en, 0); + gpio_direction_output(info->pdata->gpio_sda, 0); + gpio_direction_output(info->pdata->gpio_scl, 0); + gpio_direction_output(info->pdata->gpio_resetb, 0); + + hw_reboot_bootloader(info); + + val = flash_readl(info, ISP_IC_INFO_ADDR); + dev_info(&client->dev, "IC info: 0x%02x (%x)\n", val & 0xff, val); + + dev_info(&client->dev, "fw erase...\n"); + flash_erase(info); + if (!flash_is_erased(info)) { + ret = -ENXIO; + goto err; + } + + dev_info(&client->dev, "fw write...\n"); + /* XXX: what does this do?! */ + flash_writel(info, ISP_IC_INFO_ADDR, 0xffffff00 | (val & 0xff)); + usleep_range(1000, 1500); + ret = fw_write_image(info, data, len); + if (ret) + goto err; + usleep_range(1000, 1500); + + hw_reboot_normal(info); + usleep_range(1000, 1500); + dev_info(&client->dev, "fw download done...\n"); + return 0; + +err: + dev_err(&client->dev, "fw download failed...\n"); + hw_reboot_normal(info); + return ret; +} + +static int get_fw_version(struct mms_ts_info *info) +{ + int ret; + int retries = 3; + + /* this seems to fail sometimes after a reset.. retry a few times */ + do { + ret = i2c_smbus_read_byte_data(info->client, MMS_FW_VERSION); + } while (ret < 0 && retries-- > 0); + + return ret; +} + +static int mms_ts_enable(struct mms_ts_info *info) +{ + mutex_lock(&info->lock); + if (info->enabled) + goto out; + /* wake up the touch controller. */ + i2c_smbus_write_byte_data(info->client, 0, 0); + usleep_range(3000, 5000); + info->enabled = true; + enable_irq(info->irq); +out: + mutex_unlock(&info->lock); + return 0; +} + +static int mms_ts_disable(struct mms_ts_info *info) +{ + mutex_lock(&info->lock); + if (!info->enabled) + goto out; + disable_irq(info->irq); + i2c_smbus_write_byte_data(info->client, MMS_MODE_CONTROL, 0); + usleep_range(10000, 12000); + info->enabled = false; +out: + mutex_unlock(&info->lock); + return 0; +} + +static int mms_ts_input_open(struct input_dev *dev) +{ + struct mms_ts_info *info = input_get_drvdata(dev); + int ret; + + ret = wait_for_completion_interruptible_timeout(&info->init_done, + msecs_to_jiffies(90 * MSEC_PER_SEC)); + + if (ret > 0) { + if (info->irq != -1) + ret = mms_ts_enable(info); + else + ret = -ENXIO; + } else if (ret < 0) { + dev_err(&dev->dev, + "error while waiting for device to init (%d)\n", ret); + ret = -ENXIO; + } else if (ret == 0) { + dev_err(&dev->dev, + "timedout while waiting for device to init\n"); + ret = -ENXIO; + } + + return ret; +} + +static void mms_ts_input_close(struct input_dev *dev) +{ + struct mms_ts_info *info = input_get_drvdata(dev); + mms_ts_disable(info); +} + +static int mms_ts_finish_config(struct mms_ts_info *info) +{ + struct i2c_client *client = info->client; + int ret; + + ret = request_threaded_irq(client->irq, NULL, mms_ts_interrupt, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "mms_ts", info); + if (ret < 0) { + dev_err(&client->dev, "Failed to register interrupt\n"); + goto err_req_irq; + } + disable_irq(client->irq); + + info->irq = client->irq; + barrier(); + + dev_info(&client->dev, + "Melfas MMS-series touch controller initialized\n"); + + complete_all(&info->init_done); + return 0; + +err_req_irq: + return ret; +} + +static void mms_ts_fw_load(const struct firmware *fw, void *context) +{ + struct mms_ts_info *info = context; + struct i2c_client *client = info->client; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int ret = 0; + int ver; + int retries = 3; + struct mms_fw_image *fw_img; + + ver = get_fw_version(info); + if (ver < 0) { + ver = 0; + dev_err(&client->dev, + "can't read version, controller dead? forcing reflash"); + } + + if (!fw) { + dev_info(&client->dev, "could not find firmware file '%s'\n", + info->fw_name); + goto done; + } + + fw_img = (struct mms_fw_image *)fw->data; + if (fw_img->hdr_len != sizeof(struct mms_fw_image) || + fw_img->data_len + fw_img->hdr_len != fw->size || + fw_img->hdr_ver != 0x1) { + dev_err(&client->dev, + "firmware image '%s' invalid, may continue\n", + info->fw_name); + goto err; + } + + if (ver == fw_img->fw_ver && !mms_force_reflash) { + dev_info(&client->dev, + "fw version 0x%02x already present\n", ver); + goto done; + } + + dev_info(&client->dev, "need fw update (0x%02x != 0x%02x)\n", + ver, fw_img->fw_ver); + + if (!info->pdata || !info->pdata->mux_fw_flash) { + dev_err(&client->dev, + "fw cannot be updated, missing platform data\n"); + goto err; + } + + while (retries--) { + i2c_lock_adapter(adapter); + info->pdata->mux_fw_flash(true); + + ret = fw_download(info, fw_img->data, fw_img->data_len); + + info->pdata->mux_fw_flash(false); + i2c_unlock_adapter(adapter); + + if (ret < 0) { + dev_err(&client->dev, + "error updating firmware to version 0x%02x\n", + fw_img->fw_ver); + if (retries) + dev_err(&client->dev, "retrying flashing\n"); + continue; + } + + ver = get_fw_version(info); + if (ver == fw_img->fw_ver) { + dev_info(&client->dev, + "fw update done. ver = 0x%02x\n", ver); + goto done; + } else { + dev_err(&client->dev, + "ERROR: fw update succeeded, but fw version is still wrong (0x%x != 0x%x)\n", + ver, fw_img->fw_ver); + } + if (retries) + dev_err(&client->dev, "retrying flashing\n"); + } + + dev_err(&client->dev, "could not flash firmware, ran out of retries\n"); + BUG_ON(mms_die_on_flash_fail); + +err: + /* complete anyway, so open() doesn't get blocked */ + complete_all(&info->init_done); + goto out; + +done: + mms_ts_finish_config(info); +out: + release_firmware(fw); +} + +static int __devinit mms_ts_config(struct mms_ts_info *info, bool nowait) +{ + struct i2c_client *client = info->client; + int ret = 0; + const char *filename = info->pdata->fw_name ?: "mms144_ts.fw"; + + mms_pwr_on_reset(info); + + if (nowait) { + const struct firmware *fw; + info->fw_name = kasprintf(GFP_KERNEL, "melfas/%s", filename); + ret = request_firmware(&fw, info->fw_name, &client->dev); + if (ret) { + dev_err(&client->dev, + "error requesting built-in firmware\n"); + goto out; + } + mms_ts_fw_load(fw, info); + } else { + info->fw_name = kstrdup(filename, GFP_KERNEL); + ret = request_firmware_nowait(THIS_MODULE, true, info->fw_name, + &client->dev, GFP_KERNEL, + info, mms_ts_fw_load); + if (ret) + dev_err(&client->dev, + "cannot schedule firmware update (%d)\n", ret); + } + +out: + return ret; +} + +static int __devinit mms_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct mms_ts_info *info; + struct input_dev *input_dev; + int ret = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + return -EIO; + + info = kzalloc(sizeof(struct mms_ts_info), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!info || !input_dev) { + dev_err(&client->dev, "Failed to allocate memory\n"); + goto err_alloc; + } + + info->client = client; + info->input_dev = input_dev; + info->pdata = client->dev.platform_data; + init_completion(&info->init_done); + info->irq = -1; + mutex_init(&info->lock); + + if (info->pdata) { + info->max_x = info->pdata->max_x; + info->max_y = info->pdata->max_y; + info->invert_x = info->pdata->invert_x; + info->invert_y = info->pdata->invert_y; + } else { + info->max_x = 720; + info->max_y = 1280; + } + + input_mt_init_slots(input_dev, MAX_FINGERS); + + snprintf(info->phys, sizeof(info->phys), + "%s/input0", dev_name(&client->dev)); + input_dev->name = "Melfas MMSxxx Touchscreen"; + input_dev->phys = info->phys; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &client->dev; + input_dev->open = mms_ts_input_open; + input_dev->close = mms_ts_input_close; + + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, MAX_WIDTH, 0, 0); + input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, MAX_PRESSURE, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_X, + 0, info->max_x, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, + 0, info->max_y, 0, 0); + + input_set_drvdata(input_dev, info); + + ret = input_register_device(input_dev); + if (ret) { + dev_err(&client->dev, "failed to register input dev (%d)\n", + ret); + goto err_reg_input_dev; + } + + i2c_set_clientdata(client, info); + + ret = mms_ts_config(info, mms_flash_from_probe); + if (ret) { + dev_err(&client->dev, "failed to initialize (%d)\n", ret); + goto err_config; + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + info->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + info->early_suspend.suspend = mms_ts_early_suspend; + info->early_suspend.resume = mms_ts_late_resume; + register_early_suspend(&info->early_suspend); +#endif + + return 0; + +err_config: + input_unregister_device(input_dev); + input_dev = NULL; +err_reg_input_dev: +err_alloc: + input_free_device(input_dev); + kfree(info->fw_name); + kfree(info); + return ret; +} + +static int __devexit mms_ts_remove(struct i2c_client *client) +{ + struct mms_ts_info *info = i2c_get_clientdata(client); + + if (info->irq >= 0) + free_irq(info->irq, info); + input_unregister_device(info->input_dev); + kfree(info->fw_name); + kfree(info); + + return 0; +} + +#if defined(CONFIG_PM) || defined(CONFIG_HAS_EARLYSUSPEND) +static int mms_ts_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mms_ts_info *info = i2c_get_clientdata(client); + int i; + + /* TODO: turn off the power (set vdd_en to 0) to the touchscreen + * on suspend + */ + + mutex_lock(&info->input_dev->mutex); + if (!info->input_dev->users) + goto out; + + mms_ts_disable(info); + for (i = 0; i < MAX_FINGERS; i++) { + input_mt_slot(info->input_dev, i); + input_mt_report_slot_state(info->input_dev, MT_TOOL_FINGER, + false); + } + input_sync(info->input_dev); + +out: + mutex_unlock(&info->input_dev->mutex); + return 0; +} + +static int mms_ts_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mms_ts_info *info = i2c_get_clientdata(client); + int ret = 0; + + mutex_lock(&info->input_dev->mutex); + if (info->input_dev->users) + ret = mms_ts_enable(info); + mutex_unlock(&info->input_dev->mutex); + + return ret; +} +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mms_ts_early_suspend(struct early_suspend *h) +{ + struct mms_ts_info *info; + info = container_of(h, struct mms_ts_info, early_suspend); + mms_ts_suspend(&info->client->dev); +} + +static void mms_ts_late_resume(struct early_suspend *h) +{ + struct mms_ts_info *info; + info = container_of(h, struct mms_ts_info, early_suspend); + mms_ts_resume(&info->client->dev); +} +#endif + +#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND) +static const struct dev_pm_ops mms_ts_pm_ops = { + .suspend = mms_ts_suspend, + .resume = mms_ts_resume, +}; +#endif + +static const struct i2c_device_id mms_ts_id[] = { + { "mms_ts", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mms_ts_id); + +static struct i2c_driver mms_ts_driver = { + .probe = mms_ts_probe, + .remove = __devexit_p(mms_ts_remove), + .driver = { + .name = "mms_ts", +#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND) + .pm = &mms_ts_pm_ops, +#endif + }, + .id_table = mms_ts_id, +}; + +static int __init mms_ts_init(void) +{ + return i2c_add_driver(&mms_ts_driver); +} + +static void __exit mms_ts_exit(void) +{ + i2c_del_driver(&mms_ts_driver); +} + +module_init(mms_ts_init); +module_exit(mms_ts_exit); + +/* Module information */ +MODULE_DESCRIPTION("Touchscreen driver for Melfas MMS-series controllers"); +MODULE_LICENSE("GPL"); |