aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/input/touchscreen/mms_ts.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/touchscreen/mms_ts.c')
-rwxr-xr-xdrivers/input/touchscreen/mms_ts.c949
1 files changed, 949 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/mms_ts.c b/drivers/input/touchscreen/mms_ts.c
new file mode 100755
index 0000000..df705bd
--- /dev/null
+++ b/drivers/input/touchscreen/mms_ts.c
@@ -0,0 +1,949 @@
+/*
+ * 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);
+
+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 = &reg,
+ .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);
+ 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);
+ 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);
+ 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);
+ 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);
+ }
+ dev_err(&client->dev, "retrying flashing\n");
+ }
+
+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");