summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-exynos/common.h1
-rw-r--r--arch/arm/mach-exynos/exynos.c23
-rw-r--r--drivers/soc/samsung/Makefile2
-rw-r--r--drivers/soc/samsung/exynos-pm.c174
-rw-r--r--include/linux/soc/samsung/exynos-pm.h21
5 files changed, 197 insertions, 24 deletions
diff --git a/arch/arm/mach-exynos/common.h b/arch/arm/mach-exynos/common.h
index f4bb2c7dfb9f..0b4d24cd90c1 100644
--- a/arch/arm/mach-exynos/common.h
+++ b/arch/arm/mach-exynos/common.h
@@ -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);
diff --git a/arch/arm/mach-exynos/exynos.c b/arch/arm/mach-exynos/exynos.c
index 9a9caac1125a..1067d2f5425f 100644
--- a/arch/arm/mach-exynos/exynos.c
+++ b/arch/arm/mach-exynos/exynos.c
@@ -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"))
diff --git a/drivers/soc/samsung/Makefile b/drivers/soc/samsung/Makefile
index db2754ed4ebc..cd7af1c7ae42 100644
--- a/drivers/soc/samsung/Makefile
+++ b/drivers/soc/samsung/Makefile
@@ -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
index 000000000000..1174afce0e32
--- /dev/null
+++ b/drivers/soc/samsung/exynos-pm.c
@@ -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
index 000000000000..b1afe95ed10c
--- /dev/null
+++ b/include/linux/soc/samsung/exynos-pm.h
@@ -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 */