ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/marvell/linux/drivers/misc/ecall_event.c b/marvell/linux/drivers/misc/ecall_event.c
new file mode 100644
index 0000000..ffce411
--- /dev/null
+++ b/marvell/linux/drivers/misc/ecall_event.c
@@ -0,0 +1,302 @@
+/*
+ * GPIO edge detection driver for ASR eCall transaction
+ *
+ * Copyright 2021 ASR Microelectronics (Shanghai) Co., Ltd.
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/gpio.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/edge_wakeup_mmp.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <stddef.h>
+
+#define ECALL_DEVICE_NUM 2
+struct ecall_event_device {
+    char name[16];
+    struct device *dev;
+    int state;
+    int gpio;
+    int irq;
+    struct delayed_work work;
+    struct workqueue_struct *wq;
+    spinlock_t lock;
+};
+
+static struct class *ecall_event_class;
+static struct ecall_event_device *guedevice[ECALL_DEVICE_NUM];
+static const char* gpio_name[ECALL_DEVICE_NUM] = {
+    "gpio-auto-ecall",
+    "gpio-manual-ecall",
+};
+
+static void report_ecall_event(struct ecall_event_device *uedev, int state)
+{
+    char name_buf[50];
+    char *env[3];
+
+    snprintf(name_buf, sizeof(name_buf), "%s", uedev->name);
+    env[0] = name_buf;
+    env[1] = state ? "GPIO_UP" : "GPIO_DOWN";
+    env[2] = NULL;
+
+    if (state)
+        kobject_uevent_env(&uedev->dev->kobj, KOBJ_ONLINE, env);
+    else
+        kobject_uevent_env(&uedev->dev->kobj, KOBJ_OFFLINE, env);
+    printk(KERN_INFO"%s: uevent from %s [%s] is sent\n",
+           __func__, env[0], env[1]);
+}
+
+static void ecall_event_work(struct work_struct *work)
+{
+    struct ecall_event_device *uedev = container_of(to_delayed_work(work), struct ecall_event_device, work);
+    int state = gpio_get_value(uedev->gpio);
+
+    if (state != uedev->state) {
+        uedev->state = state;
+        report_ecall_event(uedev, state);
+    }
+}
+
+irqreturn_t ecall_event_handler(int irq, void *dev_id)
+{
+    struct ecall_event_device *uedev = (struct ecall_event_device *)dev_id;
+    unsigned long flags = 0;
+
+    spin_lock_irqsave(&uedev->lock, flags);
+    if (work_pending(&uedev->work.work))
+        cancel_delayed_work(&uedev->work);
+    queue_delayed_work(uedev->wq, &uedev->work, HZ / 4);
+    spin_unlock_irqrestore(&uedev->lock, flags);
+
+    printk(KERN_INFO"%s: irq[%d]\n", __func__, irq);
+    return IRQ_HANDLED;
+}
+
+static ssize_t ecall_send_event(struct device *dev,
+                                struct device_attribute *attr, const char *buf,
+                                size_t count)
+{
+    struct ecall_event_device *uedev = (struct ecall_event_device *)dev_get_drvdata(dev);
+    unsigned long state;
+    int ret = 0;
+
+    ret = kstrtoul(buf, 10, &state);
+    report_ecall_event(uedev, (int)state);
+    if (ret)
+        return ret;
+    else
+        return count;
+}
+
+static DEVICE_ATTR(send_event, 0220, NULL, ecall_send_event);
+static struct device_attribute *ecall_event_attr[] = {
+    &dev_attr_send_event,
+    NULL
+};
+
+static int ecall_event_create_sys_device(struct device *dev)
+{
+    int ret = 0;
+    struct device_attribute **attr = ecall_event_attr;
+
+    for (; *attr; ++attr) {
+        ret = device_create_file(dev, *attr);
+        if (ret)
+            break;
+    }
+
+    if (ret) {
+        for (--attr; attr >= ecall_event_attr; --attr)
+            device_remove_file(dev, *attr);
+    }
+    return 0;
+}
+
+static int ecall_event_remove_sys_device(struct device *dev)
+{
+    struct device_attribute **attr = ecall_event_attr;
+
+    for (; *attr; ++attr)
+        device_remove_file(dev, *attr);
+    return 0;
+}
+
+static int init_device(struct platform_device *pdev, int dev_num)
+{
+    int ret = -1;
+    unsigned long tmp;
+    struct ecall_event_device *uedevice = guedevice[dev_num];
+
+    snprintf(uedevice->name, sizeof(uedevice->name) - 1,
+             "ecall%d", dev_num);
+    spin_lock_init(&uedevice->lock);
+    uedevice->dev = device_create(ecall_event_class, NULL,
+                                  MKDEV(0, dev_num), NULL, uedevice->name);
+
+    if (IS_ERR(uedevice->dev)) {
+        printk(KERN_INFO"%s: create ecalldev failed!\n",  __func__);
+        ret = PTR_ERR(uedevice->dev);
+        goto out;
+    }
+
+    dev_set_drvdata(uedevice->dev, uedevice);
+    of_property_read_u32(pdev->dev.of_node, gpio_name[dev_num], &uedevice->gpio);
+    if (uedevice->gpio >= 0) {
+        tmp = dev_num;
+        ret = request_mfp_edge_wakeup(uedevice->gpio,
+                                      NULL,
+                                      (void *)tmp, &pdev->dev);
+        if (ret) {
+            dev_err(uedevice->dev, "failed to request edge wakeup.\n");
+            goto edge_wakeup;
+        }
+        uedevice->state = gpio_get_value(uedevice->gpio);
+    }
+
+    ret = ecall_event_create_sys_device(uedevice->dev);
+    if (ret < 0) {
+        printk(KERN_INFO"%s: create sys device failed!\n", __func__);
+        goto destroy_device;
+    }
+
+    gpio_request(uedevice->gpio, uedevice->name);
+    gpio_direction_input(uedevice->gpio);
+    INIT_DELAYED_WORK(&uedevice->work, ecall_event_work);
+    uedevice->wq = create_workqueue(uedevice->name);
+    if (uedevice->wq == NULL) {
+        printk(KERN_INFO"%s: can't create work queue!\n", __func__);
+        ret = -ENOMEM;
+        goto free_gpio;
+    }
+
+    uedevice->irq = gpio_to_irq(uedevice->gpio);
+    ret = request_irq(uedevice->irq, ecall_event_handler,
+                IRQF_SHARED | IRQF_TRIGGER_RISING |
+                IRQF_TRIGGER_FALLING | IRQF_ONESHOT, uedevice->name,
+                uedevice);
+    if (ret < 0) {
+        printk(KERN_INFO"%s: request irq failed!\n",  __func__);
+        goto destroy_wq;
+    }
+
+    device_init_wakeup(uedevice->dev, 1);
+    ret = 0;
+    goto out;
+
+destroy_wq:
+    destroy_workqueue(uedevice->wq);
+free_gpio:
+    gpio_free(uedevice->gpio);
+destroy_device:
+    device_destroy(ecall_event_class, MKDEV(0, dev_num));
+edge_wakeup:
+    if (uedevice->gpio >= 0)
+        remove_mfp_edge_wakeup(uedevice->gpio);
+out:
+    return ret;
+}
+
+static void deinit_device(int dev_num)
+{
+    struct ecall_event_device *uedevice = guedevice[dev_num];
+
+    free_irq(uedevice->irq, uedevice);
+    if (uedevice->wq != NULL)
+        destroy_workqueue(uedevice->wq);
+    if (uedevice->gpio >= 0)
+        remove_mfp_edge_wakeup(uedevice->gpio);
+    gpio_free(uedevice->gpio);
+    ecall_event_remove_sys_device(uedevice->dev);
+    device_destroy(ecall_event_class, MKDEV(0, dev_num));
+}
+
+static int ecall_event_probe(struct platform_device *pdev)
+{
+    int ret;
+    int dev_num;
+
+    if (!ecall_event_class)
+        ecall_event_class = class_create(THIS_MODULE, "ecall_event");
+    if (IS_ERR(ecall_event_class))
+        return PTR_ERR(ecall_event_class);
+
+    for (dev_num = 0; dev_num < ECALL_DEVICE_NUM; dev_num++) {
+        if (of_device_is_compatible(pdev->dev.of_node, "asr,ecall-event")) {
+            printk(KERN_INFO"%s: ecall-event\n", __func__);
+        } else {
+            printk(KERN_INFO"%s: unknown device\n", __func__);
+            goto deinit;
+        }
+
+        guedevice[dev_num] = kzalloc(sizeof(struct ecall_event_device), GFP_KERNEL);
+        ret = init_device(pdev, dev_num);
+        if (ret < 0)
+            goto deinit;
+    }
+
+    return 0;
+
+deinit:
+    deinit_device(dev_num);
+    class_destroy(ecall_event_class);
+
+    return -1;
+}
+
+static int ecall_event_remove(struct platform_device *pdev)
+{
+    int i;
+    for (i = 0; i < ECALL_DEVICE_NUM; i++)
+        deinit_device(i);
+    class_destroy(ecall_event_class);
+    for (i = 0; i < ECALL_DEVICE_NUM; i++) {
+        if (guedevice[i])
+            kfree(guedevice[i]);
+    }
+    return 0;
+}
+
+static const struct of_device_id ecall_of_match[] = {
+    { .compatible = "asr,ecall-event",},
+    {},
+};
+MODULE_DEVICE_TABLE(of, ecall_of_match);
+
+static struct platform_driver ecall_driver = {
+    .probe = ecall_event_probe,
+    .remove = ecall_event_remove,
+    .driver = {
+        .name = "ecall",
+        .owner = THIS_MODULE,
+        .of_match_table = ecall_of_match,
+    },
+};
+
+module_platform_driver(ecall_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Shuo Dai");
+MODULE_DESCRIPTION("ASR GPIO event for eCall");