misc: add gps host wake driver
Manikanta [Tue, 18 Aug 2015 06:23:39 +0000 (11:23 +0530)]
- Parse gps_en gpio from DT and create a sysfs
node to set gpio value
- Register irq for geofencing feature
- Add binding doc for gps_wake driver

bug 200127146

Change-Id: I01a8b5ee39763068649e639f4883036f06a1be6a
Signed-off-by: Manikanta <mmaddireddy@nvidia.com>
Reviewed-on: http://git-master/r/789005
GVS: Gerrit_Virtual_Submit
Reviewed-by: Nagarjuna Kristam <nkristam@nvidia.com>
Reviewed-by: Rakesh Goyal <rgoyal@nvidia.com>
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
Reviewed-on: http://git-master/r/792854
Reviewed-by: Srinivas Ramachandran <srinivasra@nvidia.com>
Reviewed-by: Ashutosh Jha <ajha@nvidia.com>

Documentation/devicetree/bindings/misc/gps_wake.txt [new file with mode: 0644]
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/gps_wake.c [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/misc/gps_wake.txt b/Documentation/devicetree/bindings/misc/gps_wake.txt
new file mode 100644 (file)
index 0000000..e1f4fc8
--- /dev/null
@@ -0,0 +1,23 @@
+This driver is implemented to parse the gps_en gpio from device tree
+and create a sysfs to control the gpio in gpsd binary. Also it
+registers a irq to enable the HW geofencing feature.
+
+SYSFS: This interface is used to toggle gps_en gpio in userspace.
+
+Required Properties:
+- compatible: Must be "nvidia,tegra-gps-wake";
+
+Optional Properties:
+- gps-en is resource name for gps_en gpio
+- gps-host-wake is resource name for host wake irq line
+
+Example:
+gps_wake {
+       compatible = "nvidia,tegra-gps-wake";
+       id = <0>;
+       gps-en = <&gpio_i2c_1_20 10 0>;
+       gps-host-wake = <&gpio TEGRA_GPIO(Q, 5) 0>;
+       interrupt-parent = <&gpio>;
+       interrupts = <TEGRA_GPIO(Q, 5) 0x01>;
+       status = "disabled";
+};
index effd690..f6fa742 100644 (file)
@@ -621,6 +621,11 @@ config BLUEDROID_PM
           Say Y here to compile support for bluedroid_pm support into the kernel
           or say M to compile it as module (bluedroid_pm).
 
+config GPS_HWGEOFENCING
+       tristate "hw geofencing support"
+       ---help---
+       This driver provides gps enable, hw geofencing and host wake capability.
+
 config CPULOAD_MONITOR
        bool "Publish cpu load measures in sysfs"
        depends on CPU_FREQ
index 5aaf9ec..81b09dc 100644 (file)
@@ -75,6 +75,7 @@ obj-$(CONFIG_SND_SOC_TEGRA_CS42L73)   += a2220.o
 obj-$(CONFIG_SND_SOC_TEGRA_RT5640)     += tfa9887.o
 obj-$(CONFIG_FAN_THERM_EST)    += therm_fan_est.o
 obj-$(CONFIG_BLUEDROID_PM)      += bluedroid_pm.o
+obj-$(CONFIG_GPS_HWGEOFENCING)  += gps_wake.o
 obj-$(CONFIG_CPULOAD_MONITOR)  += cpuload.o
 obj-$(CONFIG_SIM_MAX77660)     += max77660-sim.o
 obj-$(CONFIG_SIM_PALMAS)       += palmas-sim.o
diff --git a/drivers/misc/gps_wake.c b/drivers/misc/gps_wake.c
new file mode 100644 (file)
index 0000000..b5ea510
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2015, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/err.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+
+struct gps_wake_data {
+       int gps_en;
+       int gps_host_wake;
+       int gps_host_wake_irq;
+       struct class *gps_wake_class;
+       struct device *gps_dev;
+};
+
+static irqreturn_t gps_hostwake_isr(int irq, void *dev_id)
+{
+       /* schedule a tasklet to handle the change in the host wake line */
+       return IRQ_HANDLED;
+}
+
+static ssize_t gps_enable_show(struct device *dev,
+                       struct device_attribute *attr, char *buf)
+{
+       struct gps_wake_data *gps_wake =
+                               (struct gps_wake_data *)dev_get_drvdata(dev);
+       int state;
+
+       state = gpio_get_value_cansleep(gps_wake->gps_en);
+
+       return snprintf(buf, 1, "%d", state);
+}
+
+static ssize_t gps_enable_store(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t count)
+{
+       struct gps_wake_data *gps_wake =
+                               (struct gps_wake_data *)dev_get_drvdata(dev);
+       int state;
+
+       if (kstrtoint(buf, 10, &state) < 0)
+               return -EINVAL;
+
+       gpio_set_value_cansleep(gps_wake->gps_en, state);
+
+       return count;
+}
+
+static DEVICE_ATTR(gps_enable, S_IWUSR, gps_enable_show, gps_enable_store);
+
+static int gps_wake_probe(struct platform_device *pdev)
+{
+       struct gps_wake_data *gps_wake;
+       struct resource *res;
+       struct device_node *node;
+       int ret;
+
+       gps_wake = kmalloc(sizeof(*gps_wake), GFP_KERNEL);
+       if (!gps_wake)
+               return -ENOMEM;
+
+       if (pdev->dev.of_node) {
+               node = pdev->dev.of_node;
+
+               gps_wake->gps_en = of_get_named_gpio(node, "gps-en", 0);
+               gps_wake->gps_host_wake = of_get_named_gpio(node,
+                                               "gps-host-wake", 0);
+               gps_wake->gps_host_wake_irq = platform_get_irq(pdev, 0);
+       } else {
+               res = platform_get_resource_byname(pdev, IORESOURCE_IO,
+                                                               "gps-en");
+               if (res)
+                       gps_wake->gps_en = res->start;
+               else
+                       gps_wake->gps_en = -1;
+
+               res = platform_get_resource_byname(pdev, IORESOURCE_IO,
+                                               "gps-host-wake");
+               if (res)
+                       gps_wake->gps_host_wake = res->start;
+               else
+                       gps_wake->gps_host_wake = -1;
+
+               res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+                                                       "gps-host-wake-irq");
+               if (res)
+                       gps_wake->gps_host_wake_irq = res->start;
+               else
+                       gps_wake->gps_host_wake_irq = -1;
+
+       }
+
+       if (gpio_is_valid(gps_wake->gps_en)) {
+               /* Request gps_en gpio with output low as default direction */
+               ret = devm_gpio_request_one(&pdev->dev, gps_wake->gps_en,
+                                               GPIOF_OUT_INIT_LOW, "gps_en");
+               if (ret) {
+                       pr_err("%s: Failed to request gps_en gpio, ret=%d\n",
+                                                               __func__, ret);
+                       goto free_res1;
+               }
+
+               gps_wake->gps_wake_class = class_create(THIS_MODULE,
+                                                               "gps_wake");
+               if (IS_ERR(gps_wake->gps_wake_class)) {
+                       ret = PTR_ERR(gps_wake->gps_wake_class);
+                       pr_err("%s: Failed to create gps_wake class, ret=%d\n",
+                                                               __func__, ret);
+                       goto free_res1;
+               }
+
+               gps_wake->gps_dev = device_create(gps_wake->gps_wake_class,
+                                       NULL, 0, gps_wake, "gps_device");
+               if (IS_ERR(gps_wake->gps_dev)) {
+                       ret = PTR_ERR(gps_wake->gps_dev);
+                       pr_err("%s: Failed to create gps_device, ret=%d\n",
+                                                               __func__, ret);
+                       goto free_res2;
+               }
+
+               ret = device_create_file(gps_wake->gps_dev,
+                                                       &dev_attr_gps_enable);
+               if (ret) {
+                       pr_err("%s: Failed to create device file, ret=%d\n",
+                                                               __func__, ret);
+                       goto free_res3;
+               }
+       } else {
+               pr_debug("%s: gps_en is not a valid gpio\n", __func__);
+       }
+
+       if (gpio_is_valid(gps_wake->gps_host_wake)) {
+               /* configure host_wake as input */
+               ret = devm_gpio_request_one(&pdev->dev, gps_wake->gps_host_wake,
+                                               GPIOF_IN, "gps_host_wake");
+               if (ret) {
+                       pr_err("%s: request gps_host_wake gpio fail, ret=%d\n",
+                                                               __func__, ret);
+                       goto free_res4;
+               }
+
+               if (gps_wake->gps_host_wake_irq > -1) {
+                       pr_debug("%s: found gps_host_wake irq\n", __func__);
+                       ret = request_irq(gps_wake->gps_host_wake_irq,
+                               gps_hostwake_isr, IRQF_DISABLED |
+                               IRQF_TRIGGER_RISING, "gps hostwake", gps_wake);
+                       if (ret) {
+                               pr_err("%s: gps request_irq failed, ret=%d\n",
+                                                               __func__, ret);
+                               goto free_res4;
+                       }
+                       ret = device_init_wakeup(&pdev->dev, 1);
+                       if (ret) {
+                               pr_err("%s:device_init_wakeup failed, ret=%d\n",
+                                                               __func__, ret);
+                               goto free_res4;
+                       }
+               } else {
+                       pr_debug("%s: not a valid gps irq\n", __func__);
+               }
+
+       } else {
+               pr_debug("%s: gpio_host_wake is not a valid gpio\n", __func__);
+       }
+
+       dev_set_drvdata(&pdev->dev, gps_wake);
+       pr_debug("driver successfully registered");
+
+       return 0;
+
+free_res4:
+       if (gpio_is_valid(gps_wake->gps_en))
+               device_remove_file(gps_wake->gps_dev, &dev_attr_gps_enable);
+free_res3:
+       if (gpio_is_valid(gps_wake->gps_en))
+               device_destroy(gps_wake->gps_wake_class, 0);
+free_res2:
+       if (gpio_is_valid(gps_wake->gps_en))
+               class_destroy(gps_wake->gps_wake_class);
+free_res1:
+       kfree(gps_wake);
+
+       return -ENODEV;
+}
+
+static int gps_wake_remove(struct platform_device *pdev)
+{
+       struct gps_wake_data *gps_wake =
+                       (struct gps_wake_data *)dev_get_drvdata(&pdev->dev);
+
+       if (gpio_is_valid(gps_wake->gps_en)) {
+               device_remove_file(&pdev->dev, &dev_attr_gps_enable);
+               device_destroy(gps_wake->gps_wake_class, 0);
+               class_destroy(gps_wake->gps_wake_class);
+       }
+       if (gps_wake->gps_host_wake_irq > -1)
+               free_irq(gps_wake->gps_host_wake_irq, NULL);
+       kfree(gps_wake);
+
+       return 0;
+}
+
+static int gps_wake_suspend(struct platform_device *pdev,
+                                               pm_message_t state)
+{
+       struct gps_wake_data *gps_wake =
+                       (struct gps_wake_data *)dev_get_drvdata(&pdev->dev);
+
+       if (gps_wake->gps_host_wake_irq > -1 && device_may_wakeup(&pdev->dev))
+               enable_irq_wake(gps_wake->gps_host_wake_irq);
+       return 0;
+}
+
+static int gps_wake_resume(struct platform_device *pdev)
+{
+       struct gps_wake_data *gps_wake =
+                       (struct gps_wake_data *)dev_get_drvdata(&pdev->dev);
+
+       if (gps_wake->gps_host_wake_irq > -1 && device_may_wakeup(&pdev->dev))
+               disable_irq_wake(gps_wake->gps_host_wake_irq);
+
+       return 0;
+}
+
+static struct of_device_id gps_of_match[] = {
+       { .compatible = "nvidia,tegra-gps-wake", },
+       { },
+};
+MODULE_DEVICE_TABLE(of, gps_of_match);
+
+static struct platform_driver gps_wake_driver = {
+       .probe = gps_wake_probe,
+       .remove = gps_wake_remove,
+       .suspend = gps_wake_suspend,
+       .resume = gps_wake_resume,
+       .driver = {
+               .name = "gps_wake",
+               .of_match_table = of_match_ptr(gps_of_match),
+               .owner = THIS_MODULE,
+       },
+};
+
+module_platform_driver(gps_wake_driver);
+
+MODULE_DESCRIPTION("GPS HOST WAKE");
+MODULE_AUTHOR("NVIDIA");
+MODULE_LICENSE("GPL v2");