input: avin_detect: add avin detect driver
authorXingyu Chen <xingyu.chen@amlogic.com>
Mon, 30 Jul 2018 07:29:03 +0000 (15:29 +0800)
committerXingyu Chen <xingyu.chen@amlogic.com>
Thu, 2 Aug 2018 02:59:32 +0000 (10:59 +0800)
PD#170716: input: avin_detect: add avin detect driver

porting from the Linux-3.14

Change-Id: Id0566a62be7d587ca6368b3d29f3887bab1f6d87
Signed-off-by: Xingyu Chen <xingyu.chen@amlogic.com>
12 files changed:
MAINTAINERS
arch/arm64/boot/dts/amlogic/txl_t950_p341.dts
arch/arm64/boot/dts/amlogic/txl_t960_p346.dts
arch/arm64/boot/dts/amlogic/txl_t962_p320.dts
arch/arm64/boot/dts/amlogic/txl_t962_p321.dts
arch/arm64/configs/meson64_defconfig
drivers/amlogic/input/Kconfig
drivers/amlogic/input/Makefile
drivers/amlogic/input/avin_detect/Kconfig [new file with mode: 0644]
drivers/amlogic/input/avin_detect/Makefile [new file with mode: 0644]
drivers/amlogic/input/avin_detect/avin_detect.c [new file with mode: 0644]
drivers/amlogic/input/avin_detect/avin_detect.h [new file with mode: 0644]

index a12e9406e8cca130b611c7fa38ef296daf75d772..58d56c5066b973445c2ad383c9e76514d8f98067 100644 (file)
@@ -14554,3 +14554,7 @@ F:      Documentation/devicetree/bindings/amlogic/reboot-meson.txt
 AMLOGIC PARTITION DTSI
 M: Jiaming Huang <jiaming.huang@amlogic.com>
 F: arch/arm64/boot/dts/amlogic/partition_tv_4G.dtsi
+
+AMLOGIC AVIN DETECT DRIVER
+M:     Xingyu Chen <xingyu.chen@amlogic.com>
+F:     drivers/amlogic/input/avin_detect/*
index 9af220cbcc85ab887eee5cd27899b141167a05d2..5acfa294b52c02217f9dc56b7c347342059abc5a 100644 (file)
                key_tolerance = <40 40 40 40 40 40 40>;
        };
 
+       avin_detect {
+               compatible = "amlogic, avin_detect";
+               status = "okay";
+               avin_device_num = <2>;
+               gpios = <&gpio  GPIODV_8  GPIO_ACTIVE_HIGH>,
+                       <&gpio  GPIODV_6  GPIO_ACTIVE_HIGH>;
+               detect_interval_length = <100>;
+               set_detect_times = <5>;
+               set_fault_tolerance = <1>;
+       };
+
        picdec {
                compatible = "amlogic, picdec";
                status = "okay";
index de345e80f609052d998be9238b0668764c0dcb69..11a9e067011ac4b6617e8b65c2e7d9230e0309de 100644 (file)
                key_tolerance = <40 40 40 40 40 40 40>;
        };
 
+       avin_detect {
+               compatible = "amlogic, avin_detect";
+               status = "okay";
+               avin_device_num = <2>;
+               gpios = <&gpio  GPIODV_8  GPIO_ACTIVE_HIGH>,
+                       <&gpio  GPIODV_6  GPIO_ACTIVE_HIGH>;
+               detect_interval_length = <100>;
+               set_detect_times = <5>;
+               set_fault_tolerance = <1>;
+       };
+
        picdec {
                compatible = "amlogic, picdec";
                status = "okay";
                };
        };
        /* end AUDIO_RELATED */
-
 };
 
 &i2c1 {
index 190a66b6ce32dd9ee13575bb0ac08a326bb6395c..61853f4b67ef0824bd57db98efccbda2de306133 100644 (file)
                key_tolerance = <40 40 40 40 40 40>;
        };
 
+       avin_detect {
+               compatible = "amlogic, avin_detect";
+               status = "okay";
+               avin_device_num = <2>;
+               gpios = <&gpio  GPIODV_8   GPIO_ACTIVE_HIGH>,
+                       <&gpio  GPIODV_10  GPIO_ACTIVE_HIGH>;
+               detect_interval_length = <100>;
+               set_detect_times = <5>;
+               set_fault_tolerance = <1>;
+       };
+
        picdec {
                compatible = "amlogic, picdec";
                status = "okay";
                };
        };
        /* end AUDIO_RELATED */
-
 };
 
 &i2c0 {
index ff6e23a5f2878bda12d27f6054f91bd100e045bd..268ccca6f51dfed238f631e487718e5fad64cef4 100644 (file)
                key_tolerance = <40 40 40 40 40 40 40>;
        };
 
+       avin_detect {
+               compatible = "amlogic, avin_detect";
+               status = "okay";
+               avin_device_num = <1>;
+               gpios = <&gpio  GPIODV_8  GPIO_ACTIVE_HIGH>;
+               detect_interval_length = <100>;
+               set_detect_times = <5>;
+               set_fault_tolerance = <1>;
+       };
+
        picdec {
                compatible = "amlogic, picdec";
                status = "okay";
                };
        };
        /* end AUDIO_RELATED */
-
 };
 
 &i2c0 {
index 389af7d231f0a346495eebf82ae287b6c29c3af6..8aaa573b9369a8d61633aae6387584cc5157c3f0 100644 (file)
@@ -243,6 +243,7 @@ CONFIG_AMLOGIC_GX_CLK=y
 CONFIG_AMLOGIC_CRYPTO=y
 CONFIG_AMLOGIC_CRYPTO_DMA=y
 CONFIG_AMLOGIC_INPUT=y
+CONFIG_AMLOGIC_AVIN_DETECT=y
 CONFIG_AMLOGIC_INPUT_KEYBOARD=y
 CONFIG_AMLOGIC_ADC_KEYPADS=y
 CONFIG_AMLOGIC_GPIO_KEY=y
index 01bb956990434bc869d0b19c95fffa82654a350c..5e0685b4579e95359880fdc31f078b5080d142d0 100644 (file)
@@ -10,6 +10,8 @@ menuconfig AMLOGIC_INPUT
 
 if AMLOGIC_INPUT
 
+source "drivers/amlogic/input/avin_detect/Kconfig"
+
 source "drivers/amlogic/input/keyboard/Kconfig"
 
 source "drivers/amlogic/input/remote/Kconfig"
index ec30b73cdc79950680ae1a6f25c238fb2a54b59f..ae6f8ab2156087a2b55d7ca86c4afa0368788152 100644 (file)
@@ -4,6 +4,8 @@
 
 # Each configuration option enables a list of files.
 
+obj-$(CONFIG_AMLOGIC_AVIN_DETECT) += avin_detect/
+
 obj-$(CONFIG_AMLOGIC_INPUT_KEYBOARD) += keyboard/
 
 obj-$(CONFIG_AMLOGIC_REMOTE) += remote/
diff --git a/drivers/amlogic/input/avin_detect/Kconfig b/drivers/amlogic/input/avin_detect/Kconfig
new file mode 100644 (file)
index 0000000..d1858b5
--- /dev/null
@@ -0,0 +1,9 @@
+#
+# avin detect driver configuration
+#
+
+config AMLOGIC_AVIN_DETECT
+       tristate "tv av-in detect module support"
+       default n
+       help
+               Amlogic TV AV-IN detect management.
diff --git a/drivers/amlogic/input/avin_detect/Makefile b/drivers/amlogic/input/avin_detect/Makefile
new file mode 100644 (file)
index 0000000..1f630f8
--- /dev/null
@@ -0,0 +1,6 @@
+#
+# Makefile for AVIN detect
+#
+
+obj-$(CONFIG_AMLOGIC_AVIN_DETECT)                      += avin_detect.o
+
diff --git a/drivers/amlogic/input/avin_detect/avin_detect.c b/drivers/amlogic/input/avin_detect/avin_detect.c
new file mode 100644 (file)
index 0000000..bda6c0b
--- /dev/null
@@ -0,0 +1,626 @@
+/*
+ * 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.");
diff --git a/drivers/amlogic/input/avin_detect/avin_detect.h b/drivers/amlogic/input/avin_detect/avin_detect.h
new file mode 100644 (file)
index 0000000..0f01525
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * drivers/amlogic/input/avin_detect/avin_detect.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef _AVIN_DETECT_H_
+#define _AVIN_DETECT_H_
+
+#include <linux/cdev.h>
+#include <linux/workqueue.h>
+#include <linux/timer.h>
+#include <linux/mutex.h>
+#include <linux/gpio/consumer.h>
+
+
+#ifndef bool
+#define bool unsigned char
+#endif
+
+enum avin_status_e {
+       AVIN_STATUS_IN = 0,
+       AVIN_STATUS_OUT = 1,
+       AVIN_STATUS_UNKNOWN = 2,
+};
+enum avin_channel_e {
+       AVIN_CHANNEL1 = 0,
+       AVIN_CHANNEL2 = 1,
+       AVIN_CHANNEL3 = 2,
+};
+
+struct report_data_s {
+       enum avin_channel_e channel;
+       enum avin_status_e status;
+};
+
+struct dts_const_param_s {
+       unsigned char dts_device_num;
+       unsigned char dts_detect_times;
+       unsigned char dts_fault_tolerance;
+       unsigned char dts_interval_length;
+};
+
+/*
+ * irq_falling_times[i][j]
+ * i: number of avin device
+ * j: the times of set_detect_times  --detect_channel_times
+ */
+struct code_variable_s {
+       bool report_data_flag;
+       bool *pin_mask_irq_flag;
+       unsigned char first_time_into_loop;
+       unsigned char *loop_detect_times;
+       unsigned char *detect_channel_times;
+       unsigned char *actual_into_irq_times;
+       unsigned char *irq_falling_times;
+       struct report_data_s *report_data_s;
+       enum avin_status_e *ch_current_status;
+};
+
+struct hw_resource_s {
+       int *irq_num;
+       struct gpio_desc **pin;
+};
+
+struct avin_det_s {
+       char config_name[20];
+       dev_t  avin_devno;
+       struct device *config_dev;
+       struct class *config_class;
+       struct cdev avin_cdev;
+       struct dts_const_param_s dts_param;
+       struct code_variable_s code_variable;
+       struct hw_resource_s hw_res;
+       struct input_dev *input_dev;
+       struct timer_list timer;
+       struct mutex lock;
+       struct work_struct work_struct_update;
+       struct work_struct work_struct_maskirq;
+};
+
+#endif
+