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");