| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| /************************************************************************** |
| * csk05_ts_5button.c |
| * |
| * Create Date : 2019/07/12 |
| * |
| * Modify Date : 2021/05/12 |
| * |
| * Create by : jian.cai@amlogic.com |
| * |
| * Modify by : ziheng.li@amlogic.com |
| * |
| * Version : 1.0.1 , 2021/05/12 |
| * |
| **************************************************************************/ |
| #include <linux/device.h> |
| #include <linux/kernel.h> |
| #include <linux/i2c.h> |
| #include <linux/input.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| #include <linux/gpio.h> |
| #include <linux/miscdevice.h> |
| #include <linux/interrupt.h> |
| #include <linux/delay.h> |
| #include <linux/irq.h> |
| #include <linux/firmware.h> |
| #include <linux/platform_device.h> |
| #include <linux/workqueue.h> |
| #include <linux/slab.h> |
| #include <linux/fs.h> |
| #include <linux/proc_fs.h> |
| #include <linux/uaccess.h> |
| #include <linux/io.h> |
| #include <linux/init.h> |
| #include <linux/pci.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/gameport.h> |
| #include <linux/moduleparam.h> |
| #include <linux/mutex.h> |
| #include <linux/module.h> |
| #include <linux/of_gpio.h> |
| #include <linux/atomic.h> |
| #include <linux/kthread.h> |
| #include <linux/wait.h> |
| #include "csk05_reg.h" |
| /**************************************************************** |
| * |
| * Marco |
| * |
| ***************************************************************/ |
| #define CSK05_TS_NAME "hynitron, csk05_ts" |
| #define CSK05_DEVICE "csk05" |
| |
| //#define AML_CSK05_CHECK |
| |
| #ifdef AML_CSK05_CHECK |
| #define CSK05_CHECK_ENABLE 1 |
| #endif |
| |
| #define KEY_TYPE_RELEASE 0 |
| #define KEY_TYPE_PRESS 1 |
| |
| struct touchkey_st { |
| char keyname[32]; |
| int keycode; |
| }; |
| |
| /**************************************************************** |
| * |
| * Touch Key Driver |
| * |
| ***************************************************************/ |
| struct csk05_ts_data { |
| struct input_dev *input_dev; |
| struct device_node *node; |
| int irq; |
| |
| struct i2c_client *client; |
| |
| unsigned int reset_gpio; |
| unsigned int interrupt_gpio; |
| |
| unsigned char last_key; |
| struct touchkey_st *user_keys; |
| unsigned int user_keys_count; |
| |
| unsigned int g_reg_cfg_freq; |
| unsigned int g_reg_cfg_sensitivity; |
| unsigned int g_reg_cfg_idac; |
| unsigned int g_reg_cfg_noise; |
| |
| #ifdef AML_CSK05_CHECK |
| struct task_struct *csk05_check_task; |
| struct mutex csk05_i2c_mutex; /*i2c read and write mutex*/ |
| wait_queue_head_t csk05_check_task_wq; |
| atomic_t csk05_check_task_wakeup; |
| #endif |
| |
| }; |
| |
| static int csk05_gpio_init(struct csk05_ts_data *csk05_ts) |
| { |
| if (csk05_ts->node) { |
| csk05_ts->reset_gpio = of_get_named_gpio_flags(csk05_ts->node, |
| "reset_pin", 0, NULL); |
| if (gpio_request(csk05_ts->reset_gpio, "csk05-reset")) { |
| pr_err("%s %d gpio %d request failed!\n", __func__, |
| __LINE__, csk05_ts->reset_gpio); |
| return -EINVAL; |
| } |
| gpio_direction_output(csk05_ts->reset_gpio, 1); |
| |
| csk05_ts->interrupt_gpio = of_get_named_gpio_flags(csk05_ts->node, |
| "interrupt_pin", 0, NULL); |
| if (gpio_request(csk05_ts->interrupt_gpio, "csk05-irq-gpio")) { |
| pr_err("%s %d gpio %d request failed!\n", __func__, |
| __LINE__, csk05_ts->interrupt_gpio); |
| return -EINVAL; |
| } |
| gpio_direction_input(csk05_ts->interrupt_gpio); |
| } |
| return 0; |
| } |
| |
| static void csk05_gpio_deinit(struct csk05_ts_data *csk05_ts) |
| { |
| gpio_free(csk05_ts->reset_gpio); |
| gpio_free(csk05_ts->interrupt_gpio); |
| } |
| |
| static void csk05_gpio_reset(struct csk05_ts_data *csk05_ts) |
| { |
| gpio_set_value(csk05_ts->reset_gpio, 0); |
| msleep(30); |
| gpio_set_value(csk05_ts->reset_gpio, 1); |
| msleep(30); |
| } |
| |
| static unsigned int i2c_write_reg(struct csk05_ts_data *csk05_ts, |
| unsigned char reg, unsigned int reg_data) |
| { |
| int ret; |
| unsigned char buffer[2]; |
| struct i2c_msg msg[1]; |
| |
| if (!csk05_ts) { |
| pr_err("%s %d csk05_ts is NULL\n", __func__, __LINE__); |
| return -EINVAL; |
| } |
| |
| if (!csk05_ts->client) { |
| pr_err("%s %d client is NULL\n", __func__, __LINE__); |
| return -EINVAL; |
| } |
| |
| buffer[0] = reg; |
| buffer[1] = reg_data; |
| |
| msg[0].addr = csk05_ts->client->addr; |
| msg[0].flags = I2C_M_TEN; |
| msg[0].buf = buffer; |
| msg[0].len = sizeof(buffer); |
| |
| #ifdef AML_CSK05_CHECK |
| mutex_lock(&csk05_ts->csk05_i2c_mutex); |
| #endif |
| ret = i2c_transfer(csk05_ts->client->adapter, msg, 1); |
| #ifdef AML_CSK05_CHECK |
| mutex_unlock(&csk05_ts->csk05_i2c_mutex); |
| #endif |
| |
| if (ret < 0) |
| pr_err("%s %d i2c read error: %d\n", __func__, __LINE__, ret); |
| |
| return ret; |
| } |
| |
| static unsigned int i2c_read_reg(struct csk05_ts_data *csk05_ts, |
| unsigned char reg, unsigned char *buf, unsigned char num) |
| { |
| int ret; |
| struct i2c_msg msgs[2] = {0}; |
| |
| if (!csk05_ts) { |
| pr_err("%s %d csk05_ts is NULL\n", __func__, __LINE__); |
| return -EINVAL; |
| } |
| |
| if (!csk05_ts->client) { |
| pr_err("%s %d client is NULL\n", __func__, __LINE__); |
| return -EINVAL; |
| } |
| |
| msgs[0].addr = csk05_ts->client->addr; |
| msgs[0].flags = 0; |
| msgs[0].len = 1; |
| msgs[0].buf = ® |
| |
| msgs[1].addr = csk05_ts->client->addr; |
| msgs[1].flags = I2C_M_RD; |
| msgs[1].len = num; |
| msgs[1].buf = buf; |
| |
| #ifdef AML_CSK05_CHECK |
| mutex_lock(&csk05_ts->csk05_i2c_mutex); |
| #endif |
| |
| ret = i2c_transfer(csk05_ts->client->adapter, msgs, 2); |
| |
| #ifdef AML_CSK05_CHECK |
| mutex_unlock(&csk05_ts->csk05_i2c_mutex); |
| #endif |
| |
| if (ret < 0) |
| pr_err("%s %d i2c read error: %d\n", __func__, __LINE__, ret); |
| |
| return ret; |
| } |
| |
| static void csk05_on_key_event(struct input_dev *input_dev, |
| const char *keyname, int keycode, int event) |
| { |
| pr_debug("%s %d keycode:%d keyname:%s event:%s\n", __func__, __LINE__, |
| keycode, keyname, (event == KEY_TYPE_PRESS) ? "press" : "release"); |
| |
| input_report_key(input_dev, keycode, event); |
| input_sync(input_dev); |
| } |
| |
| /**************************************************************** |
| * |
| * csk05 initial register @ mobile active |
| * |
| * The smaller the GFREQ, the higher the sensitivity and the need to match the resistance. |
| * The bigger the GSENSITIVITY,the higher the sensitivity. |
| * The smaller the GIDAC, the higher the sensitivity. |
| ***************************************************************/ |
| |
| static int csk05_init_reg(struct csk05_ts_data *csk05_ts) |
| { |
| unsigned char buff[2] = {0}; |
| unsigned int i; |
| |
| for (i = 0; i < 3; i++) { |
| i2c_read_reg(csk05_ts, SCANCR, buff, 1); |
| if (buff[0] == 0) |
| break; |
| |
| pr_err("%s %d Reg[0x%02x]=0x%02x, %d time\n", |
| __func__, __LINE__, SCANCR, buff[0], i); |
| msleep(30); |
| } |
| if (i >= 3) |
| return -EINVAL; |
| |
| buff[0] = 0x1f; |
| i2c_write_reg(csk05_ts, CHANNELEN0, buff[0]); |
| |
| buff[0] = (csk05_ts->g_reg_cfg_freq == 0 ? |
| 6 : csk05_ts->g_reg_cfg_freq & 0xFF); |
| |
| i2c_write_reg(csk05_ts, GFREQ, buff[0]); |
| |
| buff[0] = 200; |
| buff[1] = 0; |
| i2c_write_reg(csk05_ts, GFINGERTH_L, buff[0]); |
| i2c_write_reg(csk05_ts, GFINGERTH_H, buff[1]); |
| |
| buff[0] = (csk05_ts->g_reg_cfg_sensitivity == 0 ? |
| 60 : csk05_ts->g_reg_cfg_sensitivity & 0xFF); |
| |
| i2c_write_reg(csk05_ts, GSENSITIVITY, buff[0]); |
| |
| buff[0] = (csk05_ts->g_reg_cfg_idac == 0 ? |
| 40 : csk05_ts->g_reg_cfg_idac & 0xFF); |
| |
| i2c_write_reg(csk05_ts, GIDAC, buff[0]); |
| |
| buff[0] = (csk05_ts->g_reg_cfg_noise == 0 ? |
| 80 : csk05_ts->g_reg_cfg_noise & 0xFF); |
| |
| i2c_write_reg(csk05_ts, GNOISETH, buff[0]); |
| |
| buff[0] = 0x20; |
| i2c_write_reg(csk05_ts, IRQCR, buff[0]); |
| |
| buff[0] = 0x05; |
| i2c_write_reg(csk05_ts, MOTIONSR, buff[0]); |
| |
| buff[0] = 0x01; |
| i2c_write_reg(csk05_ts, SCANCR, buff[0]); |
| |
| return 0; |
| } |
| |
| static irqreturn_t csk05_ts_eint_work(int irq, void *param) |
| { |
| unsigned char buff[3] = {0}; |
| char new_key = -1; |
| struct csk05_ts_data *csk05_ts = (struct csk05_ts_data *)param; |
| |
| i2c_read_reg(csk05_ts, MOTIONSR, buff, 3); |
| |
| new_key = buff[2]; |
| |
| if (csk05_ts->user_keys) { |
| int i = 0; |
| |
| for (i = 0; i < csk05_ts->user_keys_count; i++) { |
| if (new_key & (1 << i)) { |
| csk05_on_key_event(csk05_ts->input_dev, |
| csk05_ts->user_keys[i].keyname, |
| csk05_ts->user_keys[i].keycode, |
| KEY_TYPE_PRESS); |
| csk05_ts->last_key = buff[2]; |
| } |
| } |
| |
| if (buff[2] == 0) |
| for (i = 0; i < csk05_ts->user_keys_count; i++) |
| if (csk05_ts->last_key & (1 << i)) |
| csk05_on_key_event(csk05_ts->input_dev, |
| csk05_ts->user_keys[i].keyname, |
| csk05_ts->user_keys[i].keycode, KEY_TYPE_RELEASE); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| /**************************************************************** |
| * |
| * csk05 eint set |
| * |
| ***************************************************************/ |
| static int csk05_ts_setup_eint(struct csk05_ts_data *csk05_ts) |
| { |
| if (csk05_ts->node) { |
| csk05_ts->irq = gpio_to_irq(csk05_ts->interrupt_gpio); |
| |
| if (!csk05_ts->irq) { |
| pr_err("%s %d irq_of_parse_and_map fail!!\n", __func__, __LINE__); |
| return -EINVAL; |
| } |
| |
| if (request_threaded_irq(csk05_ts->irq, NULL, |
| csk05_ts_eint_work, |
| IRQ_TYPE_EDGE_FALLING | IRQF_ONESHOT, CSK05_DEVICE, (void *)csk05_ts)) { |
| pr_err("%s %d IRQ LINE NOT AVAILABLE!!\n", __func__, __LINE__); |
| return -EINVAL; |
| } |
| } else { |
| pr_err("%s %d null irq node!!\n", __func__, __LINE__); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| #ifdef AML_CSK05_CHECK |
| static int read_csk05_reg(struct csk05_ts_data *csk05_ts) |
| { |
| int ret = 0; |
| unsigned char buf1[1] = {0x00}; |
| |
| ret = i2c_read_reg(csk05_ts, SCANCR, buf1, 1); |
| if (ret < 0) { |
| pr_err("%s %d i2c read fail! ret=%d\n", __func__, __LINE__, ret); |
| return 1; |
| } |
| |
| ret = (buf1[0] == 0x00) ? 1 : ret; |
| |
| return ret; |
| } |
| |
| static int csk05_check(struct csk05_ts_data *csk05_ts) |
| { |
| if (read_csk05_reg(csk05_ts) == 1) |
| return 1; |
| |
| return 0; |
| } |
| |
| static int csk05_recovery(struct csk05_ts_data *csk05_ts) |
| { |
| csk05_gpio_reset(csk05_ts); |
| msleep(30); |
| csk05_init_reg(csk05_ts); |
| return 0; |
| } |
| |
| static unsigned int need_do_csk05_check(void) |
| { |
| int ret = 0; |
| |
| if (CSK05_CHECK_ENABLE) |
| ret = 1; |
| |
| return ret; |
| } |
| |
| static void csk05_is_check_enable(struct csk05_ts_data *csk05_ts, int enable) |
| { |
| if (need_do_csk05_check()) { |
| if (enable) { |
| atomic_set(&csk05_ts->csk05_check_task_wakeup, 1); |
| wake_up_interruptible(&csk05_ts->csk05_check_task_wq); |
| } else { |
| atomic_set(&csk05_ts->csk05_check_task_wakeup, 0); |
| } |
| } |
| } |
| |
| static int csk05_check_thread(void *data) |
| { |
| int ret; |
| int try_cnt = 10; |
| struct csk05_ts_data *csk05_ts = (struct csk05_ts_data *)data; |
| |
| pr_debug("%s %d start!\n", __func__, __LINE__); |
| |
| while (1) { |
| msleep(1000); |
| ret = wait_event_interruptible(csk05_ts->csk05_check_task_wq, |
| atomic_read(&csk05_ts->csk05_check_task_wakeup)); |
| if (ret < 0) { |
| pr_err("%s %d csk05 check thread wakeup fail!\n", __func__, __LINE__); |
| continue; |
| } |
| |
| ret = csk05_check(csk05_ts); |
| if (ret == 1) { |
| pr_info("%s %d csk05 check fail!,start csk05 recovery!\n", |
| __func__, __LINE__); |
| |
| while (try_cnt--) { |
| pr_debug("%s %d recovery try: %d\n", __func__, __LINE__, try_cnt); |
| |
| msleep(200); |
| |
| csk05_recovery(csk05_ts); |
| |
| ret = csk05_check(csk05_ts); |
| if (ret == 0) { |
| pr_debug("%s %d csk05 recovery success!\n", |
| __func__, __LINE__); |
| try_cnt = 10; |
| break; |
| } |
| |
| if (try_cnt == 0) { |
| pr_debug("%s %d exit csk05 check!\n", __func__, __LINE__); |
| csk05_is_check_enable(csk05_ts, 0); |
| csk05_recovery(csk05_ts); |
| } |
| } |
| } |
| |
| if (kthread_should_stop()) |
| break; |
| } |
| |
| return 0; |
| } |
| |
| #endif |
| |
| static int csk05_init_key_defines(struct csk05_ts_data *csk05_ts) |
| { |
| int ret; |
| |
| if (of_property_read_u32(csk05_ts->node, "key_num", &csk05_ts->user_keys_count)) { |
| pr_err("%s %d read key_num failed\n", __func__, __LINE__); |
| return -EINVAL; |
| } |
| |
| csk05_ts->user_keys = |
| kzalloc(csk05_ts->user_keys_count * sizeof(struct touchkey_st), GFP_KERNEL); |
| if (csk05_ts->user_keys) { |
| int i = 0; |
| |
| for (i = 0; i < csk05_ts->user_keys_count; i++) { |
| const char *string = NULL; |
| |
| ret = of_property_read_u32_index(csk05_ts->node, "key_code", |
| i, &csk05_ts->user_keys[i].keycode); |
| if (ret) |
| pr_debug("%s %d i:%d key_code property not found\n", |
| __func__, __LINE__, i); |
| |
| ret = of_property_read_string_index(csk05_ts->node, "key_name", |
| i, &string); |
| if (ret) |
| pr_debug("%s %d i:%d key_name property not found\n", |
| __func__, __LINE__, i); |
| |
| if (string) |
| strncpy(csk05_ts->user_keys[i].keyname, string, |
| sizeof(csk05_ts->user_keys[i].keyname)); |
| |
| pr_debug("%s %d i:%d key_code:%d key_name:%s\n", __func__, __LINE__, |
| i, csk05_ts->user_keys[i].keycode, csk05_ts->user_keys[i].keyname); |
| } |
| } else { |
| pr_err("%s %d kmalloc failed for touchkey\n", __func__, __LINE__); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static void csk05_init_reg_config(struct csk05_ts_data *csk05_ts) |
| { |
| if (of_property_read_u32(csk05_ts->node, "freq", |
| &csk05_ts->g_reg_cfg_freq) != 0) { |
| csk05_ts->g_reg_cfg_freq = 6; |
| pr_err("%s %d get <freq> from dts failed! default:6\n", __func__, __LINE__); |
| } |
| |
| if (of_property_read_u32(csk05_ts->node, "sensitivity", |
| &csk05_ts->g_reg_cfg_sensitivity) != 0) { |
| csk05_ts->g_reg_cfg_sensitivity = 60; |
| pr_err("%s %d get <sensitivity> from dts failed! default:60\n", __func__, __LINE__); |
| } |
| |
| if (of_property_read_u32(csk05_ts->node, "idac", |
| &csk05_ts->g_reg_cfg_idac) != 0) { |
| csk05_ts->g_reg_cfg_idac = 40; |
| pr_err("%s %d get <idac> from dts failed! default:40\n", __func__, __LINE__); |
| } |
| |
| if (of_property_read_u32(csk05_ts->node, "noise", |
| &csk05_ts->g_reg_cfg_noise) != 0) { |
| csk05_ts->g_reg_cfg_noise = 80; |
| pr_err("%s %d get <noise> from dts failed! default:80\n", __func__, __LINE__); |
| } |
| } |
| |
| /**************************************************************** |
| * |
| * csk05 i2c driver |
| * |
| ***************************************************************/ |
| static int csk05_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) |
| { |
| struct csk05_ts_data *csk05_ts; |
| int ret = 0; |
| int i = 0; |
| |
| pr_debug("%s %d\n", __func__, __LINE__); |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| ret = -ENODEV; |
| goto exit_check_functionality_failed; |
| } |
| |
| csk05_ts = kzalloc(sizeof(*csk05_ts), GFP_KERNEL); |
| if (!csk05_ts) { |
| ret = -ENOMEM; |
| pr_err("%s %d : kzalloc failed\n", __func__, __LINE__); |
| goto exit_alloc_data_failed; |
| } |
| |
| csk05_ts->node = client->dev.of_node; |
| csk05_ts->client = client; |
| |
| ret = csk05_gpio_init(csk05_ts); |
| if (ret) { |
| pr_err("%s %d failed to init csk05 pinctrl.\n", __func__, __LINE__); |
| goto exit_gpio_init_failed; |
| } |
| csk05_gpio_reset(csk05_ts); |
| |
| #ifdef AML_CSK05_CHECK |
| mutex_init(&csk05_ts->csk05_i2c_mutex); |
| atomic_set(&csk05_ts->csk05_check_task_wakeup, 0); |
| #endif |
| |
| ret = csk05_init_key_defines(csk05_ts); |
| if (ret) { |
| pr_err("%s %d failed to init csk05 pinctrl.\n", __func__, __LINE__); |
| goto exit_gpio_init_key; |
| } |
| |
| i2c_set_clientdata(client, csk05_ts); |
| |
| csk05_init_reg_config(csk05_ts); |
| ret = csk05_init_reg(csk05_ts); |
| if (ret) { |
| dev_err(&client->dev, |
| "%s: failed to init csk05 reg!\n", __func__); |
| goto exit_init_csk05_reg_failed; |
| } |
| |
| csk05_ts->input_dev = input_allocate_device(); |
| if (!csk05_ts->input_dev) { |
| ret = -ENOMEM; |
| dev_err(&client->dev, "failed to allocate input device\n"); |
| goto exit_input_dev_alloc_failed; |
| } |
| |
| __set_bit(EV_KEY, csk05_ts->input_dev->evbit); |
| __set_bit(EV_SYN, csk05_ts->input_dev->evbit); |
| |
| for (i = 0; i < csk05_ts->user_keys_count; i++) |
| __set_bit(csk05_ts->user_keys[i].keycode, csk05_ts->input_dev->keybit); |
| |
| csk05_ts->input_dev->name = CSK05_DEVICE; //dev_name(&client->dev) |
| ret = input_register_device(csk05_ts->input_dev); |
| if (ret) { |
| dev_err(&client->dev, |
| "%s: failed to register input device: %s\n", |
| __func__, dev_name(&client->dev)); |
| goto exit_input_register_device_failed; |
| } |
| |
| #ifdef AML_CSK05_CHECK |
| csk05_ts->csk05_check_task = kthread_create(csk05_check_thread, |
| (void *)csk05_ts, CSK05_DEVICE); |
| if (IS_ERR(csk05_ts->csk05_check_task)) { |
| pr_err("%s %d Unable to start kernel thread!\n", __func__, __LINE__); |
| ret = PTR_ERR(csk05_ts->csk05_check_task); |
| csk05_ts->csk05_check_task = NULL; |
| goto exit_creat_thread_failed; |
| } |
| |
| init_waitqueue_head(&csk05_ts->csk05_check_task_wq); |
| if (need_do_csk05_check()) |
| wake_up_process(csk05_ts->csk05_check_task); |
| |
| if (need_do_csk05_check()) |
| csk05_is_check_enable(csk05_ts, 1); |
| #endif |
| |
| ret = csk05_ts_setup_eint(csk05_ts); |
| if (ret) { |
| dev_err(&client->dev, |
| "%s: failed to create irq: %s\n", |
| __func__, dev_name(&client->dev)); |
| goto exit_create_irq_failed; |
| } |
| |
| return 0; |
| |
| exit_create_irq_failed: |
| #ifdef AML_CSK05_CHECK |
| kthread_stop(csk05_ts->csk05_check_task); |
| exit_creat_thread_failed: |
| #endif |
| input_unregister_device(csk05_ts->input_dev); |
| exit_input_register_device_failed: |
| input_free_device(csk05_ts->input_dev); |
| exit_input_dev_alloc_failed: |
| exit_init_csk05_reg_failed: |
| i2c_set_clientdata(client, NULL); |
| exit_gpio_init_key: |
| kfree(csk05_ts->user_keys); |
| csk05_ts->user_keys = NULL; |
| exit_gpio_init_failed: |
| csk05_gpio_deinit(csk05_ts); |
| kfree(csk05_ts); |
| exit_alloc_data_failed: |
| exit_check_functionality_failed: |
| return ret; |
| } |
| |
| static int csk05_i2c_remove(struct i2c_client *client) |
| { |
| struct csk05_ts_data *csk05_ts = i2c_get_clientdata(client); |
| |
| pr_debug("%s %d\n", __func__, __LINE__); |
| #ifdef AML_CSK05_CHECK |
| kthread_stop(csk05_ts->csk05_check_task); |
| #endif |
| input_unregister_device(csk05_ts->input_dev); |
| input_free_device(csk05_ts->input_dev); |
| |
| kfree(csk05_ts->user_keys); |
| csk05_ts->user_keys = NULL; |
| |
| csk05_gpio_deinit(csk05_ts); |
| kfree(csk05_ts); |
| i2c_set_clientdata(client, NULL); |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id csk05_i2c_id[] = { |
| { CSK05_TS_NAME, 0 }, { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, csk05_i2c_id); |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id csk05_of_match[] = { |
| {.compatible = CSK05_TS_NAME}, |
| {}, |
| }; |
| #endif |
| |
| static struct i2c_driver csk05_i2c_driver = { |
| .probe = csk05_i2c_probe, |
| .remove = csk05_i2c_remove, |
| .id_table = csk05_i2c_id, |
| .driver = { |
| .name = CSK05_TS_NAME, |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_OF |
| .of_match_table = csk05_of_match, |
| #endif |
| }, |
| }; |
| |
| int __init csk05_ts_init(void) |
| { |
| if (i2c_add_driver(&csk05_i2c_driver) < 0) { |
| pr_err("%s %d failed to register csk05 i2c driver.\n", __func__, __LINE__); |
| return -1; |
| } |
| return 0; |
| } |
| |
| void __exit csk05_ts_exit(void) |
| { |
| i2c_del_driver(&csk05_i2c_driver); |
| } |
| |
| MODULE_AUTHOR("jian.cai@amlogic.com"); |
| MODULE_AUTHOR("ziheng.li@amlogic.com"); |
| MODULE_DESCRIPTION("hynitron csk05 Touch driver"); |
| MODULE_LICENSE("GPL v2"); |
| |