soc: samsung: Add generic power-management driver for Exynos
authorChanwoo Choi <cw00.choi@samsung.com>
Fri, 9 Mar 2018 07:17:12 +0000 (16:17 +0900)
committerJunghoon Kim <jhoon20.kim@samsung.com>
Thu, 14 Feb 2019 05:57:56 +0000 (14:57 +0900)
To enter suspend, Exynos SoC requires the some machine dependent procedures.
Introduce the generic power-management driver to support those requirements
and generic interface for power state management.

Change-Id: I585b9538e50f27310b860a8ccbc3776dec756a6a
Signed-off-by: Jonghwa Lee <jonghwa3.lee@samsung.com>
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
arch/arm/mach-exynos/common.h
arch/arm/mach-exynos/exynos.c
drivers/soc/samsung/Makefile
drivers/soc/samsung/exynos-pm.c [new file with mode: 0644]
include/linux/soc/samsung/exynos-pm.h [new file with mode: 0644]

index f4bb2c7dfb9f5aa7b1de90fd2e3f47752867b4d7..0b4d24cd90c11d087dd5ff02a776950c8e8cc559 100644 (file)
@@ -122,7 +122,6 @@ void exynos_firmware_init(void);
  * Magic values for bootloader indicating chosen low power mode.
  * See also Documentation/arm/Samsung/Bootloader-interface.txt
  */
-#define EXYNOS_SLEEP_MAGIC     0x00000bad
 #define EXYNOS_AFTR_MAGIC      0xfcba0d10
 
 void exynos_set_boot_flag(unsigned int cpu, unsigned int mode);
index 9a9caac1125a25e4cae07c27cb1f807e2f594a61..1067d2f5425f236ee6b74a8cb32018ea16b8612d 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/of_fdt.h>
 #include <linux/platform_device.h>
 #include <linux/irqchip.h>
+#include <linux/soc/samsung/exynos-pm.h>
 #include <linux/soc/samsung/exynos-regs-pmu.h>
 
 #include <asm/cacheflush.h>
@@ -45,28 +46,6 @@ static struct platform_device exynos_cpuidle = {
        .id                = -1,
 };
 
-void __iomem *sysram_base_addr __ro_after_init;
-void __iomem *sysram_ns_base_addr __ro_after_init;
-
-void __init exynos_sysram_init(void)
-{
-       struct device_node *node;
-
-       for_each_compatible_node(node, NULL, "samsung,exynos4210-sysram") {
-               if (!of_device_is_available(node))
-                       continue;
-               sysram_base_addr = of_iomap(node, 0);
-               break;
-       }
-
-       for_each_compatible_node(node, NULL, "samsung,exynos4210-sysram-ns") {
-               if (!of_device_is_available(node))
-                       continue;
-               sysram_ns_base_addr = of_iomap(node, 0);
-               break;
-       }
-}
-
 static void __init exynos_init_late(void)
 {
        if (of_machine_is_compatible("samsung,exynos5440"))
index db2754ed4ebc90812b82899c9f1dec9c97055a7c..cd7af1c7ae427c8a9dc450eabb2451f816c66ff0 100644 (file)
@@ -1,5 +1,5 @@
 obj-$(CONFIG_EXYNOS_CHIPID)    += exynos-chipid.o
-obj-$(CONFIG_EXYNOS_PMU)       += exynos-pmu.o
+obj-$(CONFIG_EXYNOS_PMU)       += exynos-pmu.o exynos-pm.o
 
 obj-$(CONFIG_EXYNOS_ASV)       += exynos-asv.o
 
diff --git a/drivers/soc/samsung/exynos-pm.c b/drivers/soc/samsung/exynos-pm.c
new file mode 100644 (file)
index 0000000..1174afc
--- /dev/null
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// based on arch/arm/mach-exynos/suspend.c
+// Copyright (c) 2018 Samsung Electronics Co., Ltd.
+//
+// Exynos Power Management support driver
+
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_fdt.h>
+#include <linux/kernel.h>
+#include <linux/regulator/machine.h>
+#include <linux/syscore_ops.h>
+#include <linux/suspend.h>
+
+#include <asm/cpuidle.h>
+#include <asm/io.h>
+#include <asm/suspend.h>
+
+#include <linux/soc/samsung/exynos-pm.h>
+#include <linux/soc/samsung/exynos-pmu.h>
+
+/*
+ * The struct exynos_pm_data contains the callbacks of
+ * both struct platform_suspend_ops and syscore_ops.
+ * This structure is listed according to the call order,
+ * because the callback call order for the two structures is mixed.
+ */
+struct exynos_pm_data {
+       int (*prepare)(void);                   /* for platform_suspend_ops */
+       int (*suspend)(void);                   /* for syscore_ops */
+       int (*enter)(suspend_state_t state);    /* for platform_suspend_ops */
+       void (*resume)(void);                   /* for syscore_ops */
+       void (*finish)(void);                   /* for platform_suspend_ops */
+};
+
+static struct platform_suspend_ops exynos_pm_suspend_ops;
+static struct syscore_ops exynos_pm_syscore_ops;
+static const struct exynos_pm_data *pm_data  __ro_after_init;
+
+void __iomem *sysram_base_addr __ro_after_init;
+void __iomem *sysram_ns_base_addr __ro_after_init;
+
+static int exynos_pm_prepare(void)
+{
+       int ret;
+
+       /*
+        * REVISIT: It would be better if struct platform_suspend_ops
+        * .prepare handler get the suspend_state_t as a parameter to
+        * avoid hard-coding the suspend to mem state. It's safe to do
+        * it now only because the suspend_valid_only_mem function is
+        * used as the .valid callback used to check if a given state
+        * is supported by the platform anyways.
+        */
+       ret = regulator_suspend_prepare(PM_SUSPEND_MEM);
+       if (ret) {
+               pr_err("Failed to prepare regulators for suspend (%d)\n", ret);
+               return ret;
+       }
+
+       if (pm_data->prepare) {
+               ret = pm_data->prepare();
+               if (ret) {
+                       pr_err("Failed to prepare for suspend (%d)\n", ret);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static int exynos_pm_suspend(void)
+{
+       if (pm_data->suspend)
+               return pm_data->suspend();
+
+       return 0;
+}
+
+static int exynos_pm_enter(suspend_state_t state)
+{
+       int ret;
+
+       exynos_sys_powerdown_conf(SYS_SLEEP);
+
+       ret = pm_data->enter(state);
+       if (ret) {
+               pr_err("Failed to enter sleep\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static void exynos_pm_resume(void)
+{
+       if (pm_data->resume)
+               pm_data->resume();
+}
+
+static void exynos_pm_finish(void)
+{
+       int ret;
+
+       ret = regulator_suspend_finish();
+       if (ret)
+               pr_warn("Failed to resume regulators from suspend (%d)\n", ret);
+
+       if (pm_data->finish)
+               pm_data->finish();
+}
+
+/*
+ * Split the data between ARM architectures because it is relatively big
+ * and useless on other arch.
+ */
+#ifdef CONFIG_EXYNOS_PMU_ARM_DRIVERS
+#define exynos_pm_data_arm_ptr(data)   (&data)
+#else
+#define exynos_pm_data_arm_ptr(data)   NULL
+#endif
+
+static const struct of_device_id exynos_pm_of_device_ids[] = {
+       { /*sentinel*/ },
+};
+
+void __init exynos_sysram_init(void)
+{
+       struct device_node *np;
+
+       for_each_compatible_node(np, NULL, "samsung,exynos4210-sysram") {
+               if (!of_device_is_available(np))
+                       continue;
+               sysram_base_addr = of_iomap(np, 0);
+               break;
+       }
+
+       for_each_compatible_node(np, NULL, "samsung,exynos4210-sysram-ns") {
+               if (!of_device_is_available(np))
+                       continue;
+               sysram_ns_base_addr = of_iomap(np, 0);
+               break;
+       }
+}
+
+static int __init exynos_pm_init(void)
+{
+       const struct of_device_id *match;
+       struct device_node *np;
+
+       np = of_find_matching_node_and_match(NULL,
+                                       exynos_pm_of_device_ids, &match);
+       if (!np) {
+               pr_err("Failed to find PMU node for Exynos Power-Management\n");
+               return -ENODEV;
+       }
+       pm_data = (const struct exynos_pm_data *) match->data;
+
+       exynos_sysram_init();
+
+       exynos_pm_suspend_ops.valid     = suspend_valid_only_mem;
+       exynos_pm_suspend_ops.prepare   = exynos_pm_prepare;
+       exynos_pm_syscore_ops.suspend   = exynos_pm_suspend;
+       exynos_pm_suspend_ops.enter     = exynos_pm_enter;
+       exynos_pm_syscore_ops.resume    = exynos_pm_resume;
+       exynos_pm_suspend_ops.finish    = exynos_pm_finish;
+
+       register_syscore_ops(&exynos_pm_syscore_ops);
+       suspend_set_ops(&exynos_pm_suspend_ops);
+
+       return 0;
+}
+postcore_initcall(exynos_pm_init);
diff --git a/include/linux/soc/samsung/exynos-pm.h b/include/linux/soc/samsung/exynos-pm.h
new file mode 100644 (file)
index 0000000..b1afe95
--- /dev/null
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2018 Samsung Electronics Co., Ltd.
+//
+// Header for Exynos Power-Management support driver
+
+#ifndef __LINUX_SOC_EXYNOS_PM_H
+#define __LINUX_SOC_EXYNOS_PM_H
+
+/*
+ * Magic values for bootloader indicating chosen low power mode.
+ * See also Documentation/arm/Samsung/Bootloader-interface.txt
+ */
+#define EXYNOS_SLEEP_MAGIC     0x00000bad
+
+extern void __iomem *sysram_base_addr;
+extern void __iomem *sysram_ns_base_addr;
+
+extern void exynos_sysram_init(void);
+
+#endif /* __LINUX_SOC_EXYNOS_PMU_H */