| /* |
| * drivers/amlogic/input/avin_detect/avin_detect.c |
| * |
| * Copyright (C) 2017 Amlogic, Inc. 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 as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/input.h> |
| #include <linux/platform_device.h> |
| #include <linux/irq.h> |
| #include <linux/poll.h> |
| #include <linux/of_irq.h> |
| #include <linux/slab.h> |
| #include <uapi/linux/input.h> |
| #include <linux/of.h> |
| #include <linux/uaccess.h> |
| #include <linux/delay.h> |
| #include "avin_detect.h" |
| #include <linux/gpio.h> |
| |
| #ifndef CONFIG_OF |
| #define CONFIG_OF |
| #endif |
| |
| #undef pr_fmt |
| #define pr_fmt(fmt) "avin-detect: " fmt |
| |
| #define DEBUG_DEF 1 |
| #define INPUT_REPORT_SWITCH 0 |
| #define LOOP_DETECT_TIMES 3 |
| |
| #define MAX_AVIN_DEVICE_NUM 3 |
| #define AVIN_NAME "avin_detect" |
| #define AVIN_NAME_CH1 "avin_detect_ch1" |
| #define AVIN_NAME_CH2 "avin_detect_ch2" |
| #define AVIN_NAME_CH3 "avin_detect_ch3" |
| #define ABS_AVIN_1 0 |
| #define ABS_AVIN_2 1 |
| #define ABS_AVIN_3 2 |
| |
| static char *avin_name_ch[3] = {AVIN_NAME_CH1, AVIN_NAME_CH2, AVIN_NAME_CH3}; |
| static char avin_ch[3] = {AVIN_CHANNEL1, AVIN_CHANNEL2, AVIN_CHANNEL3}; |
| |
| static DECLARE_WAIT_QUEUE_HEAD(avin_waitq); |
| |
| static inline void avin_disable_irq(int irq) |
| { |
| struct irq_desc *desc = irq_to_desc(irq); |
| |
| if (!desc->depth) |
| disable_irq_nosync(irq); |
| } |
| |
| static inline void avin_enable_irq(int irq) |
| { |
| struct irq_desc *desc = irq_to_desc(irq); |
| |
| if (!desc->depth) |
| return; |
| |
| enable_irq(irq); |
| } |
| |
| static irqreturn_t avin_detect_handler(int irq, void *data) |
| { |
| int i; |
| struct avin_det_s *avdev = (struct avin_det_s *)data; |
| |
| for (i = 0; i <= avdev->dts_param.dts_device_num; i++) { |
| if (irq == avdev->hw_res.irq_num[i]) |
| break; |
| else if (i == avdev->dts_param.dts_device_num) |
| return IRQ_HANDLED; |
| } |
| |
| if (avdev->code_variable.loop_detect_times[i]++ |
| == LOOP_DETECT_TIMES) { |
| avdev->code_variable.irq_falling_times[ |
| i * avdev->dts_param.dts_detect_times + |
| avdev->code_variable.detect_channel_times[i]]++; |
| avdev->code_variable.pin_mask_irq_flag[i] = 1; |
| /*avdev->code_variable.loop_detect_times[i] = 0;*/ |
| schedule_work(&(avdev->work_struct_maskirq)); |
| } |
| return IRQ_HANDLED; |
| } |
| |
| /* must open irq >100ms later,then into timer handler */ |
| static void avin_timer_sr(unsigned long data) |
| { |
| int i; |
| struct avin_det_s *avdev = (struct avin_det_s *)data; |
| |
| for (i = 0; i < avdev->dts_param.dts_device_num; i++) { |
| if (avdev->code_variable.detect_channel_times[i] < |
| (avdev->dts_param.dts_detect_times-1)) { |
| avdev->code_variable.detect_channel_times[i]++; |
| if (avdev->code_variable.irq_falling_times[ |
| i * avdev->dts_param.dts_detect_times + |
| avdev->code_variable.detect_channel_times[ |
| i]-1] != 0) { |
| avdev->code_variable.loop_detect_times[i] = 0; |
| /* avin_enable_irq(avdev->hw_res.irq_num[i]); */ |
| } |
| avin_enable_irq(avdev->hw_res.irq_num[i]); |
| if (avdev->code_variable.detect_channel_times[ |
| i] == 1) { |
| avdev->code_variable.irq_falling_times[ |
| (i+1) * avdev->dts_param.dts_detect_times |
| - 1] = 0; |
| } else if (avdev->code_variable.detect_channel_times[i] |
| == (avdev->dts_param.dts_detect_times-1)) { |
| schedule_work(&(avdev->work_struct_update)); |
| } |
| } else { |
| avdev->code_variable.detect_channel_times[i] = 0; |
| avdev->code_variable.loop_detect_times[i] = 0; |
| avin_enable_irq(avdev->hw_res.irq_num[i]); |
| } |
| } |
| mod_timer(&avdev->timer, |
| jiffies+msecs_to_jiffies(avdev->dts_param.dts_interval_length)); |
| } |
| |
| static void kp_work_channel1(struct avin_det_s *avdev) |
| { |
| int i, j; |
| |
| mutex_lock(&avdev->lock); |
| for (i = 0; i < avdev->dts_param.dts_device_num; i++) { |
| for (j = 0; j < (avdev->dts_param.dts_detect_times-1); j++) { |
| if (avdev->code_variable.irq_falling_times[ |
| i * avdev->dts_param.dts_detect_times + j] == 0) |
| avdev->code_variable.actual_into_irq_times[i]++; |
| |
| avdev->code_variable.irq_falling_times[ |
| i * avdev->dts_param.dts_detect_times + j] = 0; |
| } |
| |
| if (avdev->code_variable.actual_into_irq_times[i] >= |
| ((avdev->dts_param.dts_detect_times - 1) |
| - avdev->dts_param.dts_fault_tolerance)) { |
| if (avdev->code_variable.ch_current_status[i] |
| != AVIN_STATUS_OUT) { |
| avdev->code_variable.ch_current_status[i] |
| = AVIN_STATUS_OUT; |
| #if INPUT_REPORT_SWITCH |
| input_report_abs(avdev->input_dev, |
| ABS_AVIN_1, AVIN_STATUS_OUT); |
| input_sync(avdev->input_dev); |
| #endif |
| avdev->code_variable.report_data_s[i].channel |
| = avin_ch[i]; |
| avdev->code_variable.report_data_s[i].status |
| = AVIN_STATUS_OUT; |
| avdev->code_variable.report_data_flag = 1; |
| wake_up_interruptible(&avin_waitq); |
| #if DEBUG_DEF |
| pr_info("avin ch%d current_status out!\n", i); |
| #endif |
| } |
| } else if (avdev->code_variable.actual_into_irq_times[i] <= |
| avdev->dts_param.dts_fault_tolerance) { |
| if (avdev->code_variable.ch_current_status[i] |
| != AVIN_STATUS_IN) { |
| avdev->code_variable.ch_current_status[i] |
| = AVIN_STATUS_IN; |
| #if INPUT_REPORT_SWITCH |
| input_report_abs(avdev->input_dev, |
| ABS_AVIN_1, AVIN_STATUS_IN); |
| input_sync(avdev->input_dev); |
| #endif |
| avdev->code_variable.report_data_s[i].channel |
| = avin_ch[i]; |
| avdev->code_variable.report_data_s[i].status |
| = AVIN_STATUS_IN; |
| avdev->code_variable.report_data_flag = 1; |
| wake_up_interruptible(&avin_waitq); |
| #if DEBUG_DEF |
| pr_info("avin ch%d current_status in!\n", i); |
| #endif |
| } |
| } else { |
| /*keep current status*/ |
| } |
| } |
| memset(avdev->code_variable.actual_into_irq_times, 0, |
| sizeof(avdev->code_variable.actual_into_irq_times[0]) * |
| avdev->dts_param.dts_device_num); |
| |
| mutex_unlock(&avdev->lock); |
| } |
| |
| static void update_work_update_status(struct work_struct *work) |
| { |
| struct avin_det_s *avin_data = |
| container_of(work, struct avin_det_s, work_struct_update); |
| |
| kp_work_channel1(avin_data); |
| } |
| |
| static void update_work_maskirq(struct work_struct *work) |
| { |
| int i; |
| struct avin_det_s *avdev = |
| container_of(work, struct avin_det_s, work_struct_maskirq); |
| |
| for (i = 0; i < avdev->dts_param.dts_device_num; i++) { |
| if (avdev->code_variable.pin_mask_irq_flag[i] == 1) { |
| avin_disable_irq(avdev->hw_res.irq_num[i]); |
| avdev->code_variable.pin_mask_irq_flag[i] = 0; |
| } |
| } |
| } |
| |
| static int aml_sysavin_dts_parse(struct platform_device *pdev) |
| { |
| int ret; |
| int i; |
| int state; |
| int value; |
| struct avin_det_s *avdev; |
| |
| avdev = platform_get_drvdata(pdev); |
| |
| ret = of_property_read_u32(pdev->dev.of_node, |
| "avin_device_num", &value); |
| avdev->dts_param.dts_device_num = value; |
| if (ret) { |
| pr_info("Failed to get dts_device_num.\n"); |
| goto get_avin_param_failed; |
| } else { |
| if (avdev->dts_param.dts_device_num == 0) { |
| pr_info("avin device num is 0\n"); |
| goto get_avin_param_failed; |
| } else if (avdev->dts_param.dts_device_num > |
| MAX_AVIN_DEVICE_NUM) { |
| pr_info("avin device num is > MAX NUM\n"); |
| goto get_avin_param_failed; |
| } |
| } |
| |
| ret = of_property_read_u32(pdev->dev.of_node, |
| "detect_interval_length", &value); |
| if (ret) { |
| pr_info("Failed to get dts_interval_length.\n"); |
| goto get_avin_param_failed; |
| } |
| avdev->dts_param.dts_interval_length = value; |
| |
| ret = of_property_read_u32(pdev->dev.of_node, |
| "set_detect_times", &value); |
| if (ret) { |
| pr_info("Failed to get dts_detect_times.\n"); |
| goto get_avin_param_failed; |
| } |
| avdev->dts_param.dts_detect_times = value + 1; |
| |
| ret = of_property_read_u32(pdev->dev.of_node, |
| "set_fault_tolerance", &value); |
| if (ret) { |
| pr_info("Failed to get dts_fault_tolerance.\n"); |
| goto get_avin_param_failed; |
| } |
| avdev->dts_param.dts_fault_tolerance = value; |
| |
| /* request resource of pin */ |
| avdev->hw_res.pin = |
| devm_kzalloc(&pdev->dev, (sizeof(struct gpio_desc *) |
| * avdev->dts_param.dts_device_num), GFP_KERNEL); |
| if (!avdev->hw_res.pin) { |
| state = -ENOMEM; |
| goto get_avin_param_failed; |
| } |
| for (i = 0; i < avdev->dts_param.dts_device_num; i++) { |
| avdev->hw_res.pin[i] = devm_gpiod_get_index(&pdev->dev, |
| NULL, i, GPIOD_IN); |
| |
| if (IS_ERR_OR_NULL(avdev->hw_res.pin[i])) { |
| state = -EINVAL; |
| goto get_avin_param_failed; |
| } |
| |
| gpiod_set_pull(avdev->hw_res.pin[i], GPIOD_PULL_DIS); |
| } |
| |
| /* request resource of irq num */ |
| avdev->hw_res.irq_num = |
| devm_kzalloc(&pdev->dev, (sizeof(avdev->hw_res.irq_num[0]) |
| * avdev->dts_param.dts_device_num), GFP_KERNEL); |
| if (!avdev->hw_res.irq_num) { |
| state = -ENOMEM; |
| goto get_avin_param_failed; |
| } |
| |
| for (i = 0; i < avdev->dts_param.dts_device_num; i++) |
| avdev->hw_res.irq_num[i] = gpiod_to_irq(avdev->hw_res.pin[i]); |
| |
| return 0; |
| |
| get_avin_param_failed: |
| return -EINVAL; |
| } |
| |
| static int avin_open(struct inode *inode, struct file *file) |
| { |
| int ret = 0; |
| struct avin_det_s *avindev; |
| |
| avindev = container_of(inode->i_cdev, struct avin_det_s, avin_cdev); |
| file->private_data = avindev; |
| return ret; |
| } |
| |
| static ssize_t avin_read(struct file *file, char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| unsigned long ret; |
| struct avin_det_s *avin_data = (struct avin_det_s *)file->private_data; |
| |
| /*wait_event_interruptible(avin_waitq, avin_data->report_data_flag);*/ |
| ret = copy_to_user(buf, |
| (void *)(avin_data->code_variable.report_data_s), |
| sizeof(avin_data->code_variable.report_data_s[0]) |
| * avin_data->dts_param.dts_device_num); |
| avin_data->code_variable.report_data_flag = 0; |
| return 0; |
| } |
| |
| static int avin_config_release(struct inode *inode, struct file *file) |
| { |
| file->private_data = NULL; |
| return 0; |
| } |
| |
| static unsigned int avin_poll(struct file *file, poll_table *wait) |
| { |
| unsigned int mask = 0; |
| struct avin_det_s *avin_data = (struct avin_det_s *)file->private_data; |
| |
| poll_wait(file, &avin_waitq, wait); |
| |
| if (avin_data->code_variable.report_data_flag) |
| mask |= POLLIN | POLLRDNORM; |
| return mask; |
| } |
| |
| static const struct file_operations avin_fops = { |
| .owner = THIS_MODULE, |
| .open = avin_open, |
| .read = avin_read, |
| .poll = avin_poll, |
| .release = avin_config_release, |
| }; |
| |
| static int register_avin_dev(struct avin_det_s *avin_data) |
| { |
| int ret = 0; |
| |
| ret = alloc_chrdev_region(&avin_data->avin_devno, |
| 0, 1, "avin_detect_region"); |
| if (ret < 0) { |
| pr_err("avin: failed to allocate major number\n"); |
| return -ENODEV; |
| } |
| |
| /* connect the file operations with cdev */ |
| cdev_init(&avin_data->avin_cdev, &avin_fops); |
| avin_data->avin_cdev.owner = THIS_MODULE; |
| /* connect the major/minor number to the cdev */ |
| ret = cdev_add(&avin_data->avin_cdev, avin_data->avin_devno, 1); |
| if (ret) { |
| pr_err("avin: failed to add device\n"); |
| return -ENODEV; |
| } |
| |
| strcpy(avin_data->config_name, "avin_detect"); |
| avin_data->config_class = class_create(THIS_MODULE, |
| avin_data->config_name); |
| avin_data->config_dev = device_create(avin_data->config_class, NULL, |
| avin_data->avin_devno, NULL, avin_data->config_name); |
| if (IS_ERR(avin_data->config_dev)) { |
| pr_err("avin: failed to create device node\n"); |
| ret = PTR_ERR(avin_data->config_dev); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int init_resource(struct avin_det_s *avdev) |
| { |
| int i, j; |
| |
| INIT_WORK(&(avdev->work_struct_update), update_work_update_status); |
| INIT_WORK(&(avdev->work_struct_maskirq), update_work_maskirq); |
| for (i = 0; i < avdev->dts_param.dts_device_num; i++) { |
| for (j = 0; j < avdev->dts_param.dts_detect_times; j++) |
| avdev->code_variable.irq_falling_times[ |
| i * avdev->dts_param.dts_detect_times + j] = 0; |
| |
| avdev->code_variable.loop_detect_times[i] = 0; |
| } |
| |
| /* set timer */ |
| setup_timer(&avdev->timer, avin_timer_sr, (unsigned long)avdev); |
| mod_timer(&avdev->timer, jiffies+msecs_to_jiffies(2000)); |
| |
| return 0; |
| } |
| |
| static int request_mem_resource(struct platform_device *pdev) |
| { |
| int i; |
| struct avin_det_s *avdev; |
| |
| avdev = platform_get_drvdata(pdev); |
| |
| avdev->code_variable.pin_mask_irq_flag = |
| devm_kzalloc(&pdev->dev, |
| (sizeof(avdev->code_variable.pin_mask_irq_flag[0]) * |
| avdev->dts_param.dts_device_num), GFP_KERNEL); |
| if (!avdev->code_variable.pin_mask_irq_flag) |
| return -ENOMEM; |
| |
| avdev->code_variable.loop_detect_times = |
| devm_kzalloc(&pdev->dev, |
| (sizeof(avdev->code_variable.loop_detect_times[0]) * |
| avdev->dts_param.dts_device_num), GFP_KERNEL); |
| if (!avdev->code_variable.loop_detect_times) |
| return -ENOMEM; |
| |
| avdev->code_variable.detect_channel_times = |
| devm_kzalloc(&pdev->dev, |
| (sizeof(avdev->code_variable.detect_channel_times[0]) * |
| avdev->dts_param.dts_device_num), GFP_KERNEL); |
| if (!avdev->code_variable.detect_channel_times) |
| return -ENOMEM; |
| |
| avdev->code_variable.report_data_s = |
| devm_kzalloc(&pdev->dev, |
| (sizeof(avdev->code_variable.report_data_s[0]) * |
| avdev->dts_param.dts_device_num), GFP_KERNEL); |
| if (!avdev->code_variable.report_data_s) |
| return -ENOMEM; |
| |
| avdev->code_variable.irq_falling_times = |
| devm_kzalloc(&pdev->dev, |
| (sizeof(avdev->code_variable.irq_falling_times[0]) * |
| avdev->dts_param.dts_device_num |
| * (avdev->dts_param.dts_detect_times)), GFP_KERNEL); |
| if (!avdev->code_variable.irq_falling_times) |
| return -ENOMEM; |
| |
| avdev->code_variable.actual_into_irq_times = |
| devm_kzalloc(&pdev->dev, |
| (sizeof(avdev->code_variable.actual_into_irq_times[0]) * |
| avdev->dts_param.dts_device_num), GFP_KERNEL); |
| if (!avdev->code_variable.actual_into_irq_times) |
| return -ENOMEM; |
| |
| avdev->code_variable.ch_current_status = |
| devm_kzalloc(&pdev->dev, |
| (sizeof(avdev->code_variable.ch_current_status[0]) * |
| avdev->dts_param.dts_device_num), GFP_KERNEL); |
| if (!avdev->code_variable.ch_current_status) |
| return -ENOMEM; |
| |
| for (i = 0; i < avdev->dts_param.dts_device_num; i++) |
| avdev->code_variable.ch_current_status[i] = AVIN_STATUS_UNKNOWN; |
| |
| return 0; |
| } |
| |
| int avin_detect_probe(struct platform_device *pdev) |
| { |
| int i; |
| int ret; |
| struct avin_det_s *avdev = NULL; |
| |
| avdev = devm_kzalloc(&pdev->dev, |
| sizeof(struct avin_det_s), GFP_KERNEL); |
| if (!avdev) |
| return -ENOMEM; |
| |
| platform_set_drvdata(pdev, avdev); |
| |
| ret = aml_sysavin_dts_parse(pdev); |
| if (ret) |
| return ret; |
| |
| ret = request_mem_resource(pdev); |
| if (ret) |
| return ret; |
| |
| init_resource(avdev); |
| |
| /* request irq num*/ |
| for (i = 0; i < avdev->dts_param.dts_device_num; i++) { |
| ret = devm_request_irq(&pdev->dev, avdev->hw_res.irq_num[i], |
| avin_detect_handler, IRQF_TRIGGER_FALLING, |
| avin_name_ch[i], (void *)avdev); |
| if (ret) { |
| pr_info("Unable to request irq resource.\n"); |
| return -EINVAL; |
| } |
| } |
| |
| mutex_init(&avdev->lock); |
| |
| /* register input device */ |
| avdev->input_dev = input_allocate_device(); |
| if (avdev->input_dev == 0) |
| return -ENOMEM; |
| |
| set_bit(EV_ABS, avdev->input_dev->evbit); |
| input_set_abs_params(avdev->input_dev, |
| ABS_AVIN_1, 0, 2, 0, 0); |
| input_set_abs_params(avdev->input_dev, |
| ABS_AVIN_2, 0, 2, 0, 0); |
| avdev->input_dev->name = AVIN_NAME; |
| /*avdev->input_dev->phys = "gpio_keypad/input0";*/ |
| avdev->input_dev->dev.parent = &pdev->dev; |
| avdev->input_dev->id.bustype = BUS_ISA; |
| avdev->input_dev->id.vendor = 0x5f5f; |
| avdev->input_dev->id.product = 0x6f6f; |
| avdev->input_dev->id.version = 0x7f7f; |
| |
| ret = input_register_device(avdev->input_dev); |
| if (ret < 0) { |
| pr_info("Unable to register avin input device.\n"); |
| input_free_device(avdev->input_dev); |
| return -EINVAL; |
| } |
| |
| /* register char device */ |
| ret = register_avin_dev(avdev); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int avin_detect_suspend(struct platform_device *pdev, |
| pm_message_t state) |
| { |
| int i; |
| struct avin_det_s *avdev = platform_get_drvdata(pdev); |
| |
| del_timer_sync(&avdev->timer); |
| cancel_work_sync(&avdev->work_struct_update); |
| cancel_work_sync(&avdev->work_struct_maskirq); |
| for (i = 0; i < avdev->dts_param.dts_device_num; i++) { |
| avin_disable_irq(avdev->hw_res.irq_num[i]); |
| avdev->code_variable.irq_falling_times[i] = 0; |
| avdev->code_variable.detect_channel_times[i] = 0; |
| avdev->code_variable.loop_detect_times[i] = 0; |
| } |
| pr_info("avin_detect_suspend ok.\n"); |
| return 0; |
| } |
| |
| static int avin_detect_resume(struct platform_device *pdev) |
| { |
| int i; |
| struct avin_det_s *avdev = platform_get_drvdata(pdev); |
| |
| for (i = 0; i < avdev->dts_param.dts_device_num; i++) |
| avin_enable_irq(avdev->hw_res.irq_num[i]); |
| init_resource(avdev); |
| pr_info("avin_detect_resume ok.\n"); |
| return 0; |
| } |
| |
| int avin_detect_remove(struct platform_device *pdev) |
| { |
| struct avin_det_s *avdev = platform_get_drvdata(pdev); |
| |
| input_unregister_device(avdev->input_dev); |
| input_free_device(avdev->input_dev); |
| cdev_del(&avdev->avin_cdev); |
| del_timer_sync(&avdev->timer); |
| cancel_work_sync(&avdev->work_struct_update); |
| cancel_work_sync(&avdev->work_struct_maskirq); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id avin_dt_match[] = { |
| { .compatible = "amlogic, avin_detect", |
| }, |
| {}, |
| }; |
| #else |
| #define avin_dt_match NULL |
| #endif |
| |
| static struct platform_driver avin_driver = { |
| .probe = avin_detect_probe, |
| .remove = avin_detect_remove, |
| .suspend = avin_detect_suspend, |
| .resume = avin_detect_resume, |
| .driver = { |
| .name = "avin_detect", |
| .of_match_table = avin_dt_match, |
| }, |
| }; |
| |
| module_platform_driver(avin_driver); |
| |
| MODULE_DESCRIPTION("Meson AVIN Driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Amlogic, Inc."); |