| /* drivers/input/touchscreen/cy8c_cs.c |
| * |
| * Copyright (C) 2011 HTC Corporation. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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. |
| * |
| */ |
| |
| #include <linux/input/cy8c_cs.h> |
| #include <linux/delay.h> |
| #include <linux/earlysuspend.h> |
| #include <linux/hrtimer.h> |
| #include <linux/i2c.h> |
| #include <linux/input.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <linux/gpio.h> |
| #include <linux/workqueue.h> |
| |
| #define CY8C_I2C_RETRY_TIMES (10) |
| #define CY8C_KEYLOCKTIME (1500) |
| #define CY8C_KEYLOCKRESET (6) |
| |
| struct cy8c_cs_data { |
| struct i2c_client *client; |
| struct input_dev *input_dev; |
| struct workqueue_struct *cy8c_wq; |
| struct work_struct work; |
| struct early_suspend early_suspend; |
| int use_irq; |
| struct hrtimer timer; |
| uint16_t version; |
| struct infor id; |
| uint16_t intr; |
| uint8_t vk_id; |
| uint8_t debug_level; |
| int *keycode; |
| int (*power)(int on); |
| int (*reset)(void); |
| int func_support; |
| struct workqueue_struct *wq_raw; |
| struct delayed_work work_raw; |
| }; |
| |
| static struct cy8c_cs_data *private_cs; |
| |
| static irqreturn_t cy8c_cs_irq_handler(int, void *); |
| static int disable_key; |
| static int reset_cnt; |
| |
| extern int board_build_flag(void); |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| static void cy8c_cs_early_suspend(struct early_suspend *h); |
| static void cy8c_cs_late_resume(struct early_suspend *h); |
| #endif |
| |
| int i2c_cy8c_read(struct i2c_client *client, uint8_t addr, uint8_t *data, uint8_t length) |
| { |
| int retry; |
| |
| struct i2c_msg msg[] = { |
| { |
| .addr = client->addr, |
| .flags = 0, |
| .len = 1, |
| .buf = &addr, |
| }, |
| { |
| .addr = client->addr, |
| .flags = I2C_M_RD, |
| .len = length, |
| .buf = data, |
| } |
| }; |
| |
| for (retry = 0; retry < CY8C_I2C_RETRY_TIMES; retry++) { |
| if (i2c_transfer(client->adapter, msg, 2) == 2) |
| break; |
| mdelay(10); |
| } |
| if (retry == CY8C_I2C_RETRY_TIMES) { |
| printk(KERN_INFO "[cap]i2c_read_block retry over %d\n", |
| CY8C_I2C_RETRY_TIMES); |
| return -EIO; |
| } |
| return 0; |
| |
| } |
| |
| int i2c_cy8c_write(struct i2c_client *client, uint8_t addr, uint8_t *data, uint8_t length) |
| { |
| int retry, loop_i; |
| uint8_t buf[length + 1]; |
| |
| struct i2c_msg msg[] = { |
| { |
| .addr = client->addr, |
| .flags = 0, |
| .len = length + 1, |
| .buf = buf, |
| } |
| }; |
| |
| buf[0] = addr; |
| for (loop_i = 0; loop_i < length; loop_i++) |
| buf[loop_i + 1] = data[loop_i]; |
| |
| for (retry = 0; retry < CY8C_I2C_RETRY_TIMES; retry++) { |
| if (i2c_transfer(client->adapter, msg, 1) == 1) |
| break; |
| mdelay(10); |
| } |
| |
| if (retry == CY8C_I2C_RETRY_TIMES) { |
| printk(KERN_ERR "[cap]i2c_write_block retry over %d\n", |
| CY8C_I2C_RETRY_TIMES); |
| return -EIO; |
| } |
| return 0; |
| |
| } |
| |
| int i2c_cy8c_write_byte_data(struct i2c_client *client, uint8_t addr, uint8_t value) |
| { |
| return i2c_cy8c_write(client, addr, &value, 1); |
| } |
| |
| static ssize_t diff(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| int ret = 0, i; |
| char data[8] = {0}; |
| struct cy8c_cs_data *cs; |
| |
| pr_info("[cap] %s", __func__); |
| |
| cs = private_cs; |
| ret = i2c_cy8c_write_byte_data(cs->client, CS_SELECT, CS_CMD_BASELINE); |
| if (ret < 0) { |
| pr_err("[cap] i2c Write baseline Err\n"); |
| return ret; |
| } |
| msleep(100); |
| ret = i2c_cy8c_read(cs->client, CS_BL_HB, data, ARRAY_SIZE(data)); |
| if (ret < 0) { |
| pr_err("[cap] i2c Read baseline Err\n"); |
| return ret; |
| } |
| |
| for (i = 0; i < 8 ; i += 2) |
| ret += sprintf(buf+ret, "BTN(%d)=%d, ", (i/2), |
| (data[i] << 8 | data[i+1])); |
| ret += sprintf(buf+ret, "\n"); |
| |
| return ret; |
| } |
| static DEVICE_ATTR(diff, S_IRUGO, diff, NULL); |
| |
| static ssize_t reset(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| struct cy8c_cs_data *cs; |
| cs = private_cs; |
| |
| pr_info("[cap] reset\n"); |
| cs->reset(); |
| ret = sprintf(buf, "Reset chip"); |
| return ret; |
| } |
| static DEVICE_ATTR(reset, S_IRUGO, reset, NULL); |
| |
| static ssize_t stop_report(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| int err; |
| unsigned long i = 0; |
| err = strict_strtoul(buf, 10, &i); |
| |
| if (disable_key < 2 || disable_key >= 0) { |
| disable_key = i; |
| pr_info("[cap] KEY Report %s!!\n", disable_key ? |
| "DISABLE" : "ENABLE"); |
| } else |
| pr_info("[cap] Parameter Error\n"); |
| |
| return count; |
| } |
| |
| static ssize_t show_flag(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| return sprintf(buf, "[cap] disable_key = %d\n", disable_key); |
| } |
| static DEVICE_ATTR(diskey, (S_IWUSR|S_IRUGO), show_flag, stop_report); |
| |
| static int cy8c_printcs_raw(struct cy8c_cs_data *cs, char *buf) |
| { |
| int ret = 0, pos = 0, i, j, cmd[4] = {CS_CMD_BTN1, CS_CMD_BTN2, CS_CMD_BTN3, CS_CMD_BTN4}; |
| char data[6] = {0}, capstate[3][10] = {"BL", "Raw", "Dlt"}; |
| |
| for (i = 0; i < cs->id.config; i++) { |
| ret = i2c_cy8c_write_byte_data(cs->client, CS_SELECT, cmd[i]); |
| if (ret < 0) { |
| pr_err("[cap] i2c Write inform (%d_%#x) Err\n", i+1, cmd[i]); |
| return ret; |
| } |
| msleep(50); |
| ret = i2c_cy8c_read(cs->client, CS_BL_HB, data, ARRAY_SIZE(data)); |
| if (ret < 0) { |
| pr_err("[cap] i2c Read inform (%d_%#x)) Err\n", i+1, cmd[i]); |
| return ret; |
| } |
| pos += sprintf(buf+pos, "BTN(%d)", i); |
| for (j = 0; j < 6 ; j += 2) |
| pos += sprintf(buf+pos, "%s=%d, ", capstate[j/2], |
| (data[j] << 8 | data[j+1])); |
| pos += sprintf(buf+pos, "\n"); |
| memset(data, 0, sizeof(ARRAY_SIZE(data))); |
| } |
| return pos; |
| } |
| |
| static ssize_t inform(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| int ret = 0, pos = 0; |
| char data[2] = {0}; |
| struct cy8c_cs_data *cs; |
| |
| cs = private_cs; |
| |
| if (cs->id.version < 0x10 && cs->id.version > 0x08) |
| cs->id.version = cs->id.version << 4; |
| |
| if (cs->id.version >= 0x86) { |
| memset(data, 0, sizeof(ARRAY_SIZE(data))); |
| ret = i2c_cy8c_read(cs->client, CS_INT_STATUS, data, 2); |
| if (ret < 0) { |
| pr_err("[cap] i2c Read inform INT status Err\n"); |
| return ret; |
| } |
| pos += sprintf(buf+pos, "Btn code = %x, INT status= %x\n", data[0], data[1]); |
| } |
| pos += cy8c_printcs_raw(cs, buf+pos); |
| |
| return pos; |
| } |
| static DEVICE_ATTR(inform, S_IRUGO, inform, NULL); |
| |
| static ssize_t cs_vendor_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| char data[3] = {0}; |
| int ret = 0; |
| struct cy8c_cs_data *cs; |
| cs = private_cs; |
| |
| ret = i2c_cy8c_read(cs->client, CS_FW_VERSION, data, 2); |
| if (ret < 0) { |
| pr_err("[cap] i2c Read version Err\n"); |
| return ret; |
| } |
| if (cs->id.chipid == CS_CHIPID) |
| sprintf(buf, "%s_V%x\n", CYPRESS_SS_NAME, data[0]); |
| else |
| sprintf(buf, "%s_V%x\n", CYPRESS_CS_NAME, data[0]); |
| ret += strlen(buf)+1; |
| |
| return ret; |
| } |
| static DEVICE_ATTR(vendor, S_IRUGO, cs_vendor_show, NULL); |
| |
| static ssize_t cy8c_cs_gpio_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| struct cy8c_cs_data *cs_data; |
| struct cy8c_i2c_cs_platform_data *pdata; |
| |
| cs_data = private_cs; |
| pdata = cs_data->client->dev.platform_data; |
| |
| ret = gpio_get_value(pdata->gpio_irq); |
| printk(KERN_DEBUG "[cap] GPIO_CS_INT_N=%d\n", pdata->gpio_irq); |
| sprintf(buf, "GPIO_CS_INT_N=%d\n", ret); |
| ret = strlen(buf) + 1; |
| return ret; |
| } |
| static DEVICE_ATTR(gpio, S_IRUGO, cy8c_cs_gpio_show, NULL); |
| |
| static ssize_t cy8c_cs_read_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| struct cy8c_cs_data *cs_data; |
| struct cy8c_i2c_cs_platform_data *pdata; |
| |
| cs_data = private_cs; |
| pdata = cs_data->client->dev.platform_data; |
| |
| ret = gpio_get_value(pdata->gpio_irq); |
| printk(KERN_DEBUG "GPIO_CS_INT_N=%d\n", pdata->gpio_irq); |
| ret = strlen(buf) + 1; |
| return ret; |
| } |
| static DEVICE_ATTR(read, S_IRUGO, cy8c_cs_read_show, NULL); |
| |
| static ssize_t debug_level_set(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| int i; |
| struct cy8c_cs_data *cs_data; |
| cs_data = private_cs; |
| if (sscanf(buf, "%d", &i) == 1 && i < 2) { |
| cs_data->debug_level = i; |
| pr_info("[cap] debug_level = %d\b", cs_data->debug_level); |
| } else |
| pr_info("[cap] Parameter Error\n"); |
| return count; |
| } |
| |
| static ssize_t debug_level_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct cy8c_cs_data *cs_data; |
| cs_data = private_cs; |
| return sprintf(buf, "[cap] debug_level = %d\n", cs_data->debug_level); |
| } |
| DEVICE_ATTR(debug_level, (S_IWUSR|S_IRUGO), debug_level_show, debug_level_set); |
| |
| static struct kobject *android_touchkey_kobj; |
| |
| static int cy8c_touchkey_sysfs_init(void) |
| { |
| int ret; |
| android_touchkey_kobj = kobject_create_and_add("android_key", NULL); |
| if (android_touchkey_kobj == NULL) { |
| printk(KERN_ERR "%s: subsystem_register failed\n", __func__); |
| ret = -ENOMEM; |
| return ret; |
| } |
| ret = sysfs_create_file(android_touchkey_kobj, &dev_attr_gpio.attr); |
| if (ret) { |
| printk(KERN_ERR "%s: sysfs_create_file gpio failed\n", __func__); |
| return ret; |
| } |
| ret = sysfs_create_file(android_touchkey_kobj, &dev_attr_read.attr); |
| if (ret) { |
| printk(KERN_ERR "%s: sysfs_create_file read failed\n", __func__); |
| return ret; |
| } |
| ret = sysfs_create_file(android_touchkey_kobj, &dev_attr_vendor.attr); |
| if (ret) { |
| printk(KERN_ERR "%s: sysfs_create_file vendor failed\n", __func__); |
| return ret; |
| } |
| ret = sysfs_create_file(android_touchkey_kobj, &dev_attr_inform.attr); |
| if (ret) { |
| printk(KERN_ERR "%s: sysfs_create_file inform failed\n", __func__); |
| return ret; |
| } |
| ret = sysfs_create_file(android_touchkey_kobj, &dev_attr_diff.attr); |
| if (ret) { |
| printk(KERN_ERR "%s: sysfs_create_file inform failed\n", __func__); |
| return ret; |
| } |
| ret = sysfs_create_file(android_touchkey_kobj, &dev_attr_debug_level.attr); |
| if (ret) { |
| printk(KERN_ERR "%s: sysfs_create_file debug_level failed\n", __func__); |
| return ret; |
| } |
| ret = sysfs_create_file(android_touchkey_kobj, &dev_attr_reset.attr); |
| if (ret) { |
| printk(KERN_ERR "%s: sysfs_create_file debug_level failed\n", __func__); |
| return ret; |
| } |
| ret = sysfs_create_file(android_touchkey_kobj, &dev_attr_diskey.attr); |
| if (ret) { |
| printk(KERN_ERR "%s: sysfs_create_file debug_level failed\n", __func__); |
| return ret; |
| } |
| return 0; |
| } |
| |
| static void cy8c_touchkey_sysfs_deinit(void) |
| { |
| sysfs_remove_file(android_touchkey_kobj, &dev_attr_gpio.attr); |
| sysfs_remove_file(android_touchkey_kobj, &dev_attr_read.attr); |
| sysfs_remove_file(android_touchkey_kobj, &dev_attr_vendor.attr); |
| sysfs_remove_file(android_touchkey_kobj, &dev_attr_inform.attr); |
| sysfs_remove_file(android_touchkey_kobj, &dev_attr_diff.attr); |
| sysfs_remove_file(android_touchkey_kobj, &dev_attr_debug_level.attr); |
| sysfs_remove_file(android_touchkey_kobj, &dev_attr_reset.attr); |
| sysfs_remove_file(android_touchkey_kobj, &dev_attr_diskey.attr); |
| kobject_del(android_touchkey_kobj); |
| } |
| |
| static void cy8c_rawdata_print(struct work_struct *work) |
| { |
| char buf[150] = {0}; |
| int pos = 0; |
| struct cy8c_cs_data *cs = container_of(work, struct cy8c_cs_data, |
| work_raw.work); |
| pos += cy8c_printcs_raw(cs, buf+pos); |
| pos = strlen(buf)+1; |
| pr_info("[cap]%s\n", buf); |
| |
| if (cs->vk_id) { |
| reset_cnt++; |
| if (reset_cnt % CY8C_KEYLOCKRESET == 0) { |
| pr_info("[cap] keylock reset\n"); |
| cs->reset(); |
| reset_cnt = 0; |
| cs->vk_id = 0; |
| } |
| queue_delayed_work(cs->wq_raw, &cs->work_raw, |
| msecs_to_jiffies(CY8C_KEYLOCKTIME-500)); |
| } |
| } |
| |
| static int cy8c_init_sensor(struct cy8c_cs_data *cs, struct cy8c_i2c_cs_platform_data *pdata) |
| { |
| uint8_t ver[2] = {0}, chip[2] = {0}; |
| int ret = 0; |
| pr_info("[cap] %s\n", __func__); |
| |
| ret = i2c_cy8c_read(cs->client, CS_FW_CHIPID, chip, 1); |
| if (ret < 0) { |
| printk(KERN_ERR "[cap_err] Chip Read Err\n"); |
| goto err_fw_get_fail; |
| } |
| |
| ret = i2c_cy8c_read(cs->client, CS_FW_VERSION, ver, 1); |
| if (!ret) |
| cs->id.version = ver[0]; |
| else { |
| printk(KERN_ERR "[cap_err] Ver Read Err\n"); |
| goto err_fw_get_fail; |
| } |
| |
| if (chip[0] == CS_CHIPID) { |
| cs->id.chipid = chip[0]; |
| pr_info("[cap] CY8C_Smart_V%x\n", cs->id.version); |
| } else |
| pr_info("[cap] CY8C_Cap_V%x\n", cs->id.version); |
| |
| ver[0] = 0; |
| ret = i2c_cy8c_read(cs->client, CS_FW_KEYCFG, ver, 1); |
| if (ret < 0) { |
| printk(KERN_ERR "[cap_err] Config Read Err\n"); |
| goto err_fw_get_fail; |
| } else { |
| if ((ver[0] != 0) && (ver[0] == CS_KEY_3 || ver[0] == CS_KEY_4)) |
| cs->id.config = ver[0]; |
| else |
| cs->id.config = 0; |
| pr_info("[cap] config = %d\n", cs->id.config); |
| } |
| return 0; |
| |
| err_fw_get_fail: |
| return ret; |
| } |
| |
| static void report_key_func(struct cy8c_cs_data *cs, uint8_t vk) |
| { |
| int ret = 0; |
| if ((cs->debug_level & 0x01) || board_build_flag() > 0) |
| pr_info("[cap] vk = %x\n", vk); |
| |
| if (vk) { |
| switch (vk) { |
| case 0x01: |
| input_report_key(cs->input_dev, cs->keycode[0], 1); |
| cs->vk_id = vk; |
| break; |
| case 0x02: |
| input_report_key(cs->input_dev, cs->keycode[1], 1); |
| cs->vk_id = vk; |
| break; |
| case 0x04: |
| input_report_key(cs->input_dev, cs->keycode[2], 1); |
| cs->vk_id = vk; |
| break; |
| case 0x08: |
| input_report_key(cs->input_dev, cs->keycode[3], 1); |
| cs->vk_id = vk; |
| break; |
| } |
| #if defined(CONFIG_TOUCH_KEY_FILTER) |
| blocking_notifier_call_chain(&touchkey_notifier_list, 1, NULL); |
| #endif |
| } else { |
| switch (cs->vk_id) { |
| case 0x01: |
| input_report_key(cs->input_dev, cs->keycode[0], 0); |
| break; |
| case 0x02: |
| input_report_key(cs->input_dev, cs->keycode[1], 0); |
| break; |
| case 0x04: |
| input_report_key(cs->input_dev, cs->keycode[2], 0); |
| break; |
| case 0x08: |
| input_report_key(cs->input_dev, cs->keycode[3], 0); |
| break; |
| } |
| cs->vk_id = 0; |
| } |
| input_sync(cs->input_dev); |
| |
| if (cs->func_support & CS_FUNC_PRINTRAW) { |
| if (cs->vk_id) { |
| queue_delayed_work(cs->wq_raw, &cs->work_raw, |
| msecs_to_jiffies(CY8C_KEYLOCKTIME)); |
| } else { |
| ret = cancel_delayed_work_sync(&cs->work_raw); |
| if (!ret) |
| cancel_delayed_work(&cs->work_raw); |
| } |
| } |
| } |
| |
| static void cy8c_cs_work_func(struct work_struct *work) |
| { |
| struct cy8c_cs_data *cs; |
| uint8_t buf[3] = {0}; |
| static uint8_t pre_buf[3] = {0}; |
| |
| cs = container_of(work, struct cy8c_cs_data, work); |
| |
| if (i2c_cy8c_read(cs->client, CS_STATUS, buf, 2) < 0) { |
| memset(buf, 0, sizeof(buf)); |
| memset(pre_buf, 0, sizeof(pre_buf)); |
| pr_err("[cap_err] %s i2c read fail", __func__); |
| goto enableirq; |
| } |
| |
| if (!disable_key) |
| report_key_func(cs, buf[0]); |
| |
| memcpy(pre_buf, buf, 2); |
| enableirq: |
| if (!cs->use_irq) |
| hrtimer_start(&cs->timer, ktime_set(0, 20000000), HRTIMER_MODE_REL); |
| else |
| enable_irq(cs->client->irq); |
| } |
| |
| #if 1 |
| static enum hrtimer_restart cy8c_cs_timer_func(struct hrtimer *timer) |
| { |
| struct cy8c_cs_data *cs; |
| |
| cs = container_of(timer, struct cy8c_cs_data, timer); |
| queue_work(cs->cy8c_wq, &cs->work); |
| return HRTIMER_NORESTART; |
| } |
| #endif |
| #if 1 |
| static irqreturn_t cy8c_cs_irq_handler(int irq, void *dev_id) |
| { |
| struct cy8c_cs_data *cs = dev_id; |
| |
| disable_irq_nosync(cs->client->irq); |
| queue_work(cs->cy8c_wq, &cs->work); |
| return IRQ_HANDLED; |
| } |
| #endif |
| |
| static int cy8c_cs_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct cy8c_cs_data *cs; |
| struct cy8c_i2c_cs_platform_data *pdata; |
| int ret = 0; |
| |
| printk(KERN_DEBUG "[cap] %s: enter\n", __func__); |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| printk(KERN_ERR "[cap_err] need I2C_FUNC_I2C\n"); |
| ret = -ENODEV; |
| goto err_check_functionality_failed; |
| } |
| |
| cs = kzalloc(sizeof(struct cy8c_cs_data), GFP_KERNEL); |
| if (cs == NULL) { |
| printk(KERN_ERR "[cap_err] allocate cy8c_cs_data failed\n"); |
| ret = -ENOMEM; |
| goto err_alloc_data_failed; |
| } |
| |
| cs->client = client; |
| i2c_set_clientdata(client, cs); |
| pdata = client->dev.platform_data; |
| |
| if (pdata) { |
| pdata->reset(); |
| msleep(50); |
| cs->intr = pdata->gpio_irq; |
| } |
| |
| if (cy8c_init_sensor(cs, pdata) < 0) { |
| pr_err("[cap_err] init failure, not probe up driver\n"); |
| goto err_init_sensor_failed; |
| } |
| if (pdata) { |
| if (pdata->id.config != cs->id.config) { |
| pr_info("[cap] pdata ++\n"); |
| pdata++; |
| } |
| } |
| |
| cs->cy8c_wq = create_singlethread_workqueue("cypress_touchkey"); |
| if (!cs->cy8c_wq) { |
| printk(KERN_ERR "[cap_err] create_singlethread_workqueue cy8c_wq fail\n"); |
| goto err_create_wq_failed; |
| } |
| INIT_WORK(&cs->work, cy8c_cs_work_func); |
| |
| cs->input_dev = input_allocate_device(); |
| if (cs->input_dev == NULL) { |
| ret = -ENOMEM; |
| printk(KERN_ERR "[cap_err] Failed to allocate input device\n"); |
| goto err_input_dev_alloc_failed; |
| } |
| cs->input_dev->name = "cy8c-touchkey"; |
| cs->input_dev->id.product = cs->id.chipid; |
| cs->input_dev->id.version = cs->id.version; |
| cs->func_support = pdata->func_support; |
| cs->keycode = pdata->keycode; |
| cs->reset = pdata->reset; |
| |
| set_bit(EV_SYN, cs->input_dev->evbit); |
| set_bit(EV_KEY, cs->input_dev->evbit); |
| |
| set_bit(KEY_BACK, cs->input_dev->keybit); |
| set_bit(KEY_HOME, cs->input_dev->keybit); |
| set_bit(KEY_APP_SWITCH, cs->input_dev->keybit); |
| set_bit(KEY_MENU, cs->input_dev->keybit); |
| |
| set_bit(KEY_SEARCH, cs->input_dev->keybit); |
| set_bit(KEY_WEIBO, cs->input_dev->keybit); |
| |
| ret = input_register_device(cs->input_dev); |
| if (ret) { |
| printk(KERN_ERR "[cap_err] unable to register %s input device\n", |
| cs->input_dev->name); |
| |
| goto err_input_register_device_failed; |
| } |
| |
| private_cs = cs; |
| |
| if (cs->func_support & CS_FUNC_PRINTRAW) { |
| pr_info("[cap]support_keylock(%x)\n", cs->func_support); |
| cs->wq_raw = create_singlethread_workqueue("CY8C_print_rawdata"); |
| if (!cs->wq_raw) { |
| pr_err("[cap]allocate cy8c_cs_print_rawdata failed\n"); |
| ret = -ENOMEM; |
| goto err_input_register_device_failed; |
| } |
| INIT_DELAYED_WORK(&cs->work_raw, cy8c_rawdata_print); |
| } |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| cs->early_suspend.level = EARLY_SUSPEND_LEVEL_STOP_DRAWING; |
| cs->early_suspend.suspend = cy8c_cs_early_suspend; |
| cs->early_suspend.resume = cy8c_cs_late_resume; |
| register_early_suspend(&cs->early_suspend); |
| #endif |
| cy8c_touchkey_sysfs_init(); |
| |
| cs->use_irq = 1; |
| if (client->irq && cs->use_irq) { |
| ret = request_irq(client->irq, cy8c_cs_irq_handler, |
| IRQF_TRIGGER_FALLING, |
| cs->id.chipid == CS_CHIPID ? CYPRESS_SS_NAME : CYPRESS_CS_NAME, |
| cs); |
| if (ret < 0) { |
| dev_err(&client->dev, "[cap_err]request_irq failed\n"); |
| printk(KERN_ERR "[cap_err] request_irq failed for gpio %d," |
| " irq %d\n", cs->intr, client->irq); |
| } |
| } |
| |
| if (!cs->use_irq) { |
| hrtimer_init(&cs->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); |
| cs->timer.function = cy8c_cs_timer_func; |
| hrtimer_start(&cs->timer, ktime_set(1, 0), HRTIMER_MODE_REL); |
| } |
| |
| return 0; |
| |
| err_input_register_device_failed: |
| input_free_device(cs->input_dev); |
| |
| err_input_dev_alloc_failed: |
| destroy_workqueue(cs->cy8c_wq); |
| err_init_sensor_failed: |
| err_create_wq_failed: |
| kfree(cs); |
| |
| err_alloc_data_failed: |
| err_check_functionality_failed: |
| return ret; |
| } |
| |
| static int cy8c_cs_remove(struct i2c_client *client) |
| { |
| struct cy8c_cs_data *cs = i2c_get_clientdata(client); |
| |
| cy8c_touchkey_sysfs_deinit(); |
| |
| unregister_early_suspend(&cs->early_suspend); |
| free_irq(client->irq, cs); |
| input_unregister_device(cs->input_dev); |
| |
| kfree(cs); |
| |
| return 0; |
| } |
| |
| static int cy8c_cs_suspend(struct i2c_client *client, pm_message_t mesg) |
| { |
| int ret; |
| struct cy8c_cs_data *cs = i2c_get_clientdata(client); |
| |
| pr_info("[cap] %s\n", __func__); |
| |
| if (cs->func_support & CS_FUNC_PRINTRAW) { |
| ret = cancel_delayed_work_sync(&cs->work_raw); |
| if (!ret) |
| cancel_delayed_work(&cs->work_raw); |
| } |
| if (client->irq && cs->use_irq) { |
| disable_irq(client->irq); |
| ret = cancel_work_sync(&cs->work); |
| if (ret) |
| enable_irq(client->irq); |
| } |
| i2c_cy8c_write_byte_data(client, CS_MODE, CS_CMD_DSLEEP); |
| return 0; |
| } |
| |
| static int cy8c_cs_resume(struct i2c_client *client) |
| { |
| struct cy8c_cs_data *cs = i2c_get_clientdata(client); |
| |
| pr_info("[cap] %s\n", __func__); |
| cs->reset(); |
| |
| msleep(50); |
| |
| if (client->irq && cs->use_irq) |
| enable_irq(client->irq); |
| return 0; |
| } |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| static void cy8c_cs_early_suspend(struct early_suspend *h) |
| { |
| struct cy8c_cs_data *ts; |
| ts = container_of(h, struct cy8c_cs_data, early_suspend); |
| cy8c_cs_suspend(ts->client, PMSG_SUSPEND); |
| } |
| |
| static void cy8c_cs_late_resume(struct early_suspend *h) |
| { |
| struct cy8c_cs_data *ts; |
| ts = container_of(h, struct cy8c_cs_data, early_suspend); |
| cy8c_cs_resume(ts->client); |
| } |
| #endif |
| |
| static const struct i2c_device_id cy8c_cs_id[] = { |
| { CYPRESS_CS_NAME, 0 }, |
| }; |
| |
| static struct i2c_driver cy8c_cs_driver = { |
| .probe = cy8c_cs_probe, |
| .remove = cy8c_cs_remove, |
| .id_table = cy8c_cs_id, |
| #ifndef CONFIG_HAS_EARLYSUSPEND |
| .suspend = cy8c_cs_suspend, |
| .resume = cy8c_cs_resume, |
| #endif |
| .driver = { |
| .name = CYPRESS_CS_NAME, |
| }, |
| }; |
| |
| static int __init cy8c_cs_init(void) |
| { |
| printk(KERN_INFO "[cap] %s: enter\n", __func__); |
| return i2c_add_driver(&cy8c_cs_driver); |
| } |
| |
| static void __exit cy8c_cs_exit(void) |
| { |
| i2c_del_driver(&cy8c_cs_driver); |
| } |
| |
| module_init(cy8c_cs_init); |
| module_exit(cy8c_cs_exit); |
| |
| MODULE_DESCRIPTION("cy8c_cs driver"); |
| MODULE_LICENSE("GPL"); |