};
};
};
+
+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>;
+ ...
+ };
+ };
+};
#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>
#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>
#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>
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,
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;
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);
--- /dev/null
+/*
+ * 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