]> rtime.felk.cvut.cz Git - hercules2020/nv-tegra/linux-4.4.git/commitdiff
soc/tegra: pmc: add wake up event bindings for PMC
authorJoseph Lo <josephl@nvidia.com>
Tue, 18 Oct 2016 07:39:21 +0000 (15:39 +0800)
committermobile promotions <svcmobile_promotions@nvidia.com>
Tue, 27 Dec 2016 09:24:26 +0000 (01:24 -0800)
The PMC is the only device that can wake up the system from deep sleep
mode (i.e. LP0). There are some wake up events in the PMC wake mask
registers that can be used to trigger PMC to wake up the system. The PMC
wake mask register defines which devices or signals can be the source to
trigger the PMC waking up. If the devices support waking up the system
from deep sleep mode, then it needs to describe a property for PMC wake
up events. This property defines the usage.

Bug 1811733

Change-Id: Ifa9bb2813383d29a93ba21ac8680d0e23441bd05
Signed-off-by: Joseph Lo <josephl@nvidia.com>
Reviewed-on: http://git-master/r/1254300
GVS: Gerrit_Virtual_Submit
Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt
drivers/soc/tegra/pmc.c
include/dt-bindings/soc/tegra-pmc.h [new file with mode: 0644]

index 968503e229e19f05e27f7cfb355f80ccc74c34f7..31775ee587fb153908952af2d684902e4d32acd2 100644 (file)
@@ -216,3 +216,67 @@ Example:
                         };
                 };
         };
+
+Wake up events
+
+The PMC is the only device that can wake up the system from deep sleep
+mode (i.e. LP0). There are some wake up events in the PMC wake mask
+register that can be used to trigger PMC to wake up the system. The PMC
+wake mask register defines which devices or siganls can be the source to
+trigger the PMC waking up. If the devices support waking up the system
+from deep sleep mode, then it needs to describe a property for PMC wake
+up events. This property defines the usage.
+
+Required properties when nvidia,suspend-mode=<0>:
+ - nvidia,pmc-wakeup : <pmc_phandle event_type event_offset trigger_type>
+                     pmc_phandle: the phandle of PMC device tree node
+                     event_type: 0 = PMC_WAKE_TYPE_GPIO
+                                 1 = PMC_WAKE_TYPE_EVENT
+                     event_offset: the offset of PMC wake mask register
+                     trigger_type: set 0 when event_type is PMC_WAKE_TYPE_GPIO
+                                   if event_type is PMC_WAKE_TYPE_EVENT
+                                   0 = PMC_TRIGGER_TYPE_NONE
+                                   1 = PMC_TRIGGER_TYPE_RISING
+                                   2 = PMC_TRIGGER_TYPE_FALLING
+                                   4 = PMC_TRIGGER_TYPE_HIGH
+                                   8 = PMC_TRIGGER_TYPE_LOW
+                     The assignments of event_type and trigger_type can be
+                     found in header file <dt-bindings/soc/tegra-pmc.h>.
+ - #nvidia,wake-cells : should be 3
+
+Example:
+
+/ SoC dts including file
+pmc: pmc {
+       compatible = "nvidia,tegra114-pmc";
+       reg = <0x7000e400 0x400>;
+       clocks = <&tegra_car 261>, <&clk32k_in>;
+       clock-names = "pclk", "clk32k_in";
+};
+
+/ Tegra board dts file
+{
+       ...
+       pmc {
+               ...
+               nvidia,suspend-mode = <0>;
+               #nvidia,wake-cells = <3>;
+               ...
+       };
+       ...
+       pmic {
+               ...
+               nvidia,pmc-wakeup = <&pmc
+                               PMC_WAKE_TYPE_EVENT 18 PMC_TRIGGER_TYPE_LOW>;
+               ...
+       };
+       ...
+       gpio-keys {
+               power {
+                       ...
+                       nvidia,pmc-wakeup = <&pmc
+                               PMC_WAKE_TYPE_GPIO 16 PMC_TRIGGER_TYPE_NONE>;
+                       ...
+               };
+       };
+};
index 234174794b929e34f612e28dce1c78f0a3024ab5..44806aa4e80da733ed0c1cdafff417be35359ddd 100644 (file)
 #include <linux/delay.h>
 #include <linux/err.h>
 #include <linux/export.h>
+#include <linux/gpio.h>
 #include <linux/init.h>
+#include <linux/interrupt.h>
 #include <linux/io.h>
+#include <linux/irq.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
+#include <linux/of_gpio.h>
 #include <linux/of_platform.h>
 #include <linux/pinctrl/pinctrl.h>
 #include <linux/pinctrl/pinconf-generic.h>
@@ -40,6 +44,7 @@
 #include <linux/reboot.h>
 #include <linux/reset.h>
 #include <linux/seq_file.h>
+#include <linux/slab.h>
 #include <linux/spinlock.h>
 #include <linux/tegra-soc.h>
 #include <linux/platform/tegra/io-dpd.h>
@@ -47,6 +52,8 @@
 #include <linux/notifier.h>
 #include <linux/regulator/consumer.h>
 
+#include <dt-bindings/soc/tegra-pmc.h>
+
 #include <soc/tegra/common.h>
 #include <soc/tegra/fuse.h>
 #include <soc/tegra/pmc.h>
@@ -421,6 +428,28 @@ struct tegra_pmc {
        struct pinctrl_desc pinctrl_desc;
 };
 
+#ifdef CONFIG_PM_SLEEP
+#define PMC_WAKE_TYPE_INDEX    0
+#define PMC_WAKE_MASK_INDEX    1
+#define PMC_TRIGGER_TYPE_INDEX 2
+#define PMC_OF_ARGS_COUNT      3
+struct pmc_wakeup {
+       u32 wake_type;
+       u32 wake_mask_offset;
+       u32 irq_num;
+       struct list_head list;
+};
+
+struct pmc_lp0_wakeup {
+       struct device_node *of_node;
+       u64 enable;
+       u64 level;
+       u64 level_any;
+       struct list_head wake_list;
+};
+static struct pmc_lp0_wakeup tegra_lp0_wakeup;
+#endif
+
 static struct tegra_pmc *pmc = &(struct tegra_pmc) {
        .base = NULL,
        .suspend_mode = TEGRA_SUSPEND_NONE,
@@ -1430,6 +1459,133 @@ void tegra_pmc_fuse_enable_mirroring(void)
 EXPORT_SYMBOL(tegra_pmc_fuse_enable_mirroring);
 
 #ifdef CONFIG_PM_SLEEP
+static void tegra_pmc_add_wakeup_event(struct of_phandle_args *ph_args,
+                                      struct device *dev,
+                                      struct device_node *np)
+{
+       struct platform_device *pdev;
+       struct pmc_wakeup *pmc_wake_source;
+       struct irq_desc *irqd;
+       struct irq_data *irq_data;
+       int pmc_wake_type, wake;
+       int irq, pmc_trigger_type;
+
+       if (ph_args->np != tegra_lp0_wakeup.of_node)
+               return;
+       if (ph_args->args_count != PMC_OF_ARGS_COUNT)
+               return;
+
+       pdev = to_platform_device(dev);
+       irq = platform_get_irq(pdev, 0);
+       pmc_wake_type = ph_args->args[PMC_WAKE_TYPE_INDEX];
+
+       switch (pmc_wake_type) {
+       case PMC_WAKE_TYPE_GPIO:
+               if (irq < 0) {
+                       int gpio;
+
+                       gpio = of_get_named_gpio(np, "gpios", 0);
+                       irq = gpio_to_irq(gpio);
+                       if (WARN_ON(irq < 0))
+                               return;
+               }
+               irqd = irq_to_desc(irq);
+               irq_data = &irqd->irq_data;
+               pmc_trigger_type = irqd_get_trigger_type(irq_data);
+               break;
+       case PMC_WAKE_TYPE_EVENT:
+               pmc_trigger_type = ph_args->args[PMC_TRIGGER_TYPE_INDEX];
+               break;
+       default:
+               return;
+       }
+
+       pmc_wake_source = kzalloc(sizeof(*pmc_wake_source), GFP_KERNEL);
+       if (!pmc_wake_source)
+               return;
+
+       pmc_wake_source->wake_type = pmc_wake_type;
+       pmc_wake_source->irq_num = irq;
+       pmc_wake_source->wake_mask_offset = ph_args->args[PMC_WAKE_MASK_INDEX];
+       wake = pmc_wake_source->wake_mask_offset;
+
+       list_add_tail(&pmc_wake_source->list, &tegra_lp0_wakeup.wake_list);
+
+       tegra_lp0_wakeup.enable |= 1ULL << wake;
+       switch (pmc_trigger_type) {
+       case IRQF_TRIGGER_FALLING:
+       case IRQF_TRIGGER_LOW:
+               tegra_lp0_wakeup.level &= ~(1ULL << wake);
+               tegra_lp0_wakeup.level_any &= ~(1ULL << wake);
+               break;
+       case IRQF_TRIGGER_HIGH:
+       case IRQF_TRIGGER_RISING:
+               tegra_lp0_wakeup.level |= (1ULL << wake);
+               tegra_lp0_wakeup.level_any &= ~(1ULL << wake);
+               break;
+       case IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING:
+               tegra_lp0_wakeup.level_any |= (1ULL << wake);
+               break;
+       default:
+               break;
+       }
+}
+
+static void tegra_of_device_add_pmc_wake(struct device *dev)
+{
+       struct of_phandle_args ph_args;
+       struct device_node *np = NULL;
+       int child_node_num, i = 0;
+
+       child_node_num = of_get_child_count(dev->of_node);
+       if (child_node_num == 0) {
+               while (!of_parse_phandle_with_args(dev->of_node,
+                                                  "nvidia,pmc-wakeup",
+                                                  "#nvidia,wake-cells",
+                                                  i++, &ph_args))
+                       tegra_pmc_add_wakeup_event(&ph_args, dev, dev->of_node);
+       } else {
+               for_each_child_of_node(dev->of_node, np) {
+                       i = 0;
+                       while (!of_parse_phandle_with_args(np,
+                                                          "nvidia,pmc-wakeup",
+                                                          "#nvidia,wake-cells",
+                                                          i++, &ph_args))
+                               tegra_pmc_add_wakeup_event(&ph_args, dev, np);
+               }
+       }
+
+       of_node_put(ph_args.np);
+}
+
+static int tegra_pmc_wake_notifier_call(struct notifier_block *nb,
+                                       unsigned long event, void *data)
+{
+       struct device *dev = data;
+
+       switch (event) {
+       case BUS_NOTIFY_BOUND_DRIVER:
+               if (dev->of_node)
+                       tegra_of_device_add_pmc_wake(dev);
+               break;
+       }
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block tegra_pmc_wake_notifier = {
+       .notifier_call = tegra_pmc_wake_notifier_call,
+};
+
+static int __init tegra_pmc_lp0_wakeup_init(void)
+{
+       if (!soc_is_tegra())
+               return 0;
+
+       bus_register_notifier(&platform_bus_type, &tegra_pmc_wake_notifier);
+       return 0;
+}
+arch_initcall(tegra_pmc_lp0_wakeup_init);
+
 enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void)
 {
        return pmc->suspend_mode;
@@ -2764,6 +2920,11 @@ static int __init tegra_pmc_early_init(void)
 
        tegra_pmc_writel(value, PMC_CNTRL);
 
+#ifdef CONFIG_PM_SLEEP
+       tegra_lp0_wakeup.of_node = np;
+       INIT_LIST_HEAD(&tegra_lp0_wakeup.wake_list);
+#endif
+
        return 0;
 }
 early_initcall(tegra_pmc_early_init);
diff --git a/include/dt-bindings/soc/tegra-pmc.h b/include/dt-bindings/soc/tegra-pmc.h
new file mode 100644 (file)
index 0000000..3c64dee
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*
+ * This header provides constants for the binding nvidia,tegra20-pmc
+ */
+
+#ifndef _DT_BINDINGS_TEGRA_PMC_H_
+#define _DT_BINDINGS_TEGRA_PMC_H_
+
+#define PMC_WAKE_TYPE_GPIO     0
+#define PMC_WAKE_TYPE_EVENT    1
+
+#define PMC_TRIGGER_TYPE_NONE          0
+#define PMC_TRIGGER_TYPE_RISING                1
+#define PMC_TRIGGER_TYPE_FALLING       2
+#define PMC_TRIGGER_TYPE_HIGH          4
+#define PMC_TRIGGER_TYPE_LOW           8
+
+#endif