summaryrefslogtreecommitdiff
authorXingyu Chen <xingyu.chen@amlogic.com>2018-07-30 07:29:03 (GMT)
committer Xingyu Chen <xingyu.chen@amlogic.com>2018-08-02 02:59:32 (GMT)
commitb5aba319aa402e56210d479891c99dc9388670be (patch)
tree2317794b474557bc6c799be286d35280da02656b
parent8c9d51363c6b2bfa0f59fb68751dbed2bfc0e21b (diff)
downloadcommon-b5aba319aa402e56210d479891c99dc9388670be.zip
common-b5aba319aa402e56210d479891c99dc9388670be.tar.gz
common-b5aba319aa402e56210d479891c99dc9388670be.tar.bz2
input: avin_detect: add avin detect driver
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>
Diffstat
-rw-r--r--MAINTAINERS4
-rw-r--r--arch/arm64/boot/dts/amlogic/txl_t950_p341.dts11
-rw-r--r--arch/arm64/boot/dts/amlogic/txl_t960_p346.dts12
-rw-r--r--arch/arm64/boot/dts/amlogic/txl_t962_p320.dts12
-rw-r--r--arch/arm64/boot/dts/amlogic/txl_t962_p321.dts11
-rw-r--r--arch/arm64/configs/meson64_defconfig1
-rw-r--r--drivers/amlogic/input/Kconfig2
-rw-r--r--drivers/amlogic/input/Makefile2
-rw-r--r--drivers/amlogic/input/avin_detect/Kconfig9
-rw-r--r--drivers/amlogic/input/avin_detect/Makefile6
-rw-r--r--drivers/amlogic/input/avin_detect/avin_detect.c626
-rw-r--r--drivers/amlogic/input/avin_detect/avin_detect.h94
12 files changed, 787 insertions, 3 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index a12e940..58d56c5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -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/*
diff --git a/arch/arm64/boot/dts/amlogic/txl_t950_p341.dts b/arch/arm64/boot/dts/amlogic/txl_t950_p341.dts
index 9af220c..5acfa29 100644
--- a/arch/arm64/boot/dts/amlogic/txl_t950_p341.dts
+++ b/arch/arm64/boot/dts/amlogic/txl_t950_p341.dts
@@ -203,6 +203,17 @@
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";
diff --git a/arch/arm64/boot/dts/amlogic/txl_t960_p346.dts b/arch/arm64/boot/dts/amlogic/txl_t960_p346.dts
index de345e8..11a9e06 100644
--- a/arch/arm64/boot/dts/amlogic/txl_t960_p346.dts
+++ b/arch/arm64/boot/dts/amlogic/txl_t960_p346.dts
@@ -204,6 +204,17 @@
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";
@@ -990,7 +1001,6 @@
};
};
/* end AUDIO_RELATED */
-
};
&i2c1 {
diff --git a/arch/arm64/boot/dts/amlogic/txl_t962_p320.dts b/arch/arm64/boot/dts/amlogic/txl_t962_p320.dts
index 190a66b..61853f4 100644
--- a/arch/arm64/boot/dts/amlogic/txl_t962_p320.dts
+++ b/arch/arm64/boot/dts/amlogic/txl_t962_p320.dts
@@ -195,6 +195,17 @@
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";
@@ -980,7 +991,6 @@
};
};
/* end AUDIO_RELATED */
-
};
&i2c0 {
diff --git a/arch/arm64/boot/dts/amlogic/txl_t962_p321.dts b/arch/arm64/boot/dts/amlogic/txl_t962_p321.dts
index ff6e23a..268ccca 100644
--- a/arch/arm64/boot/dts/amlogic/txl_t962_p321.dts
+++ b/arch/arm64/boot/dts/amlogic/txl_t962_p321.dts
@@ -195,6 +195,16 @@
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";
@@ -985,7 +995,6 @@
};
};
/* end AUDIO_RELATED */
-
};
&i2c0 {
diff --git a/arch/arm64/configs/meson64_defconfig b/arch/arm64/configs/meson64_defconfig
index 389af7d..8aaa573 100644
--- a/arch/arm64/configs/meson64_defconfig
+++ b/arch/arm64/configs/meson64_defconfig
@@ -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
diff --git a/drivers/amlogic/input/Kconfig b/drivers/amlogic/input/Kconfig
index 01bb956..5e0685b 100644
--- a/drivers/amlogic/input/Kconfig
+++ b/drivers/amlogic/input/Kconfig
@@ -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"
diff --git a/drivers/amlogic/input/Makefile b/drivers/amlogic/input/Makefile
index ec30b73..ae6f8ab 100644
--- a/drivers/amlogic/input/Makefile
+++ b/drivers/amlogic/input/Makefile
@@ -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
index 0000000..d1858b5
--- a/dev/null
+++ b/drivers/amlogic/input/avin_detect/Kconfig
@@ -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
index 0000000..1f630f8
--- a/dev/null
+++ b/drivers/amlogic/input/avin_detect/Makefile
@@ -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
index 0000000..bda6c0b
--- a/dev/null
+++ b/drivers/amlogic/input/avin_detect/avin_detect.c
@@ -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
index 0000000..0f01525
--- a/dev/null
+++ b/drivers/amlogic/input/avin_detect/avin_detect.h
@@ -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
+