[PATCH RFC v2 09/16] arm: domain: Add platform callbacks for domain power on/off

Lina Iyer lina.iyer at linaro.org
Fri Jun 26 20:02:29 PDT 2015


Platform drivers may have additional setup inorder before the domain can
be powered off. Allow, platform drivers to register power on/off
callbacks against a domain provider.

While registering the callback ensure that the domain is neither in
power on/off state. The domain should be active. To ensure that the
platform callback registration doesntrace with genpd power on/off,
execute the registration from a CPU on that domain.

Signed-off-by: Lina Iyer <lina.iyer at linaro.org>
---
 arch/arm/include/asm/cpu.h       |  1 -
 arch/arm/include/asm/pm_domain.h | 27 ++++++++++++++
 arch/arm/kernel/domains.c        | 81 +++++++++++++++++++++++++++++++++++++++-
 3 files changed, 107 insertions(+), 2 deletions(-)
 create mode 100644 arch/arm/include/asm/pm_domain.h

diff --git a/arch/arm/include/asm/cpu.h b/arch/arm/include/asm/cpu.h
index 2744f06..4a7e346 100644
--- a/arch/arm/include/asm/cpu.h
+++ b/arch/arm/include/asm/cpu.h
@@ -22,5 +22,4 @@ struct cpuinfo_arm {
 };
 
 DECLARE_PER_CPU(struct cpuinfo_arm, cpu_data);
-
 #endif
diff --git a/arch/arm/include/asm/pm_domain.h b/arch/arm/include/asm/pm_domain.h
new file mode 100644
index 0000000..d13c291
--- /dev/null
+++ b/arch/arm/include/asm/pm_domain.h
@@ -0,0 +1,27 @@
+/*
+ *  arch/arm/include/asm/pm_domain.h
+ *
+ *  Copyright (C) 2015 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef __ASM_ARM_PM_DOMAIN_H
+#define __ASM_ARM_PM_DOMAIN_H
+
+#include <linux/pm_domain.h>
+
+#if IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)
+extern int register_platform_domain_handlers(struct of_phandle_args *args,
+		int (*pd_down)(struct generic_pm_domain *),
+		int (*pd_up)(struct generic_pm_domain *));
+#else
+static inline
+int register_platform_domain_handlers(struct of_phandle_args *args,
+		int (*pd_down)(struct generic_pm_domain *),
+		int (*pd_up)(struct generic_pm_domain *))
+{ return -ENODEV; }
+#endif
+
+#endif
diff --git a/arch/arm/kernel/domains.c b/arch/arm/kernel/domains.c
index 680c3fb..2f5ba3f 100644
--- a/arch/arm/kernel/domains.c
+++ b/arch/arm/kernel/domains.c
@@ -9,10 +9,19 @@
 #include <linux/module.h>
 #include <linux/platform_device.h>
 
+#include <asm/pm_domain.h>
+
 #define NAME_MAX 16
 
+struct platform_cb {
+	int (*power_off)(struct generic_pm_domain *);
+	int (*power_on)(struct generic_pm_domain *);
+};
+
 struct arm_pm_domain {
 	struct generic_pm_domain genpd;
+	struct platform_cb plat_handler;
+	struct spinlock_t lock;
 };
 
 static inline
@@ -23,16 +32,85 @@ struct arm_pm_domain *to_arm_pd(struct generic_pm_domain *d)
 
 static int arm_pd_power_down(struct generic_pm_domain *genpd)
 {
+	struct arm_pm_domain *arm_pd = to_arm_pd(genpd);
+
+	if (arm_pd->plat_handler.power_off)
+		return arm_pd->plat_handler.power_off(genpd);
+
 	/* pr_info("KJH: %s: %s\n", __func__, genpd->name); */
 	return 0;
 }
 
 static int arm_pd_power_up(struct generic_pm_domain *genpd)
 {
+	struct arm_pm_domain *arm_pd = to_arm_pd(genpd);
+
+	if (arm_pd->plat_handler.power_on)
+		return arm_pd->plat_handler.power_on(genpd);
+
 	/* pr_info("KJH: %s: %s\n", __func__, genpd->name); */
 	return 0;
 }
 
+static void __register_platform_domain_handlers(void *data)
+{
+	struct device *cpu_dev = get_cpu_device(smp_processor_id());
+	struct generic_pm_domain *genpd;
+	struct arm_pm_domain *arm_pd;
+	unsigned long flags;
+	struct platform_cb *handler = data;
+
+	genpd = pd_to_genpd(cpu_dev->pm_domain);
+	BUG_ON(IS_ERR_OR_NULL(genpd));
+	arm_pd = to_arm_pd(genpd);
+
+	/*
+	 * Lock to avoid race with other CPUs in the same domain
+	 * trying to set the function pointers.
+	 */
+	spin_lock_irqsave(&arm_pd->lock, flags);
+	arm_pd->plat_handler.power_on = handler->power_on;
+	arm_pd->plat_handler.power_off = handler->power_off;
+	spin_unlock_irqrestore(&arm_pd->lock, flags);
+}
+
+int register_platform_domain_handlers(struct of_phandle_args *args,
+		int (*power_on)(struct generic_pm_domain *),
+		int (*power_off)(struct generic_pm_domain *))
+{
+	struct platform_cb handler;
+	struct generic_pm_domain *genpd, *p;
+	struct device *cpu_dev;
+	int cpu;
+
+	handler.power_on = power_on;
+	handler.power_off = power_off;
+
+	genpd = of_genpd_get_from_provider(args);
+	get_online_cpus();
+	for_each_online_cpu(cpu) {
+		cpu_dev = get_cpu_device(cpu);
+		p = pm_genpd_lookup_dev(cpu_dev);
+		/*
+		 * Execute the registration on the requested 'cpu', so we dont
+		 * race with the domain->power_off or ->power_on calls.
+		 * By executing on that 'cpu', we are assured that the 'cpu' is
+		 * awake, therefore the domain. This will avoid a spin lock
+		 * between power on/off calls and this function.
+		 */
+		if (p == genpd) {
+			smp_call_function_single(cpu,
+				__register_platform_domain_handlers,
+				&handler, true);
+			break;
+		}
+	}
+	put_online_cpus();
+
+	return 0;
+}
+EXPORT_SYMBOL(register_platform_domain_handlers);
+
 static void run_cpu(void *unused)
 {
 	struct device *cpu_dev = get_cpu_device(smp_processor_id());
@@ -91,7 +169,7 @@ static int arm_domain_cpu_init(void)
 		/* FIXME: this is open-coding of_cpu_device_node_get(), but I want handle to cpu_dev */
 		cpu_dev = get_cpu_device(cpuid);
 		if (!cpu_dev) {
- 			pr_warn("%s: Unable to get device for CPU%d\n", __func__, cpuid);
+			pr_warn("%s: Unable to get device for CPU%d\n", __func__, cpuid);
 			return -ENODEV;
 		}
 
@@ -152,6 +230,7 @@ static int arm_domain_init(void)
 		pd->genpd.power_off = arm_pd_power_down;
 		pd->genpd.power_on = arm_pd_power_up;
 		pd->genpd.flags |= GENPD_FLAG_IRQ_SAFE;
+		spin_lock_init(&pd->lock);
 		platform_set_drvdata(pdev, pd);
 
 		dev_dbg(dev, "adding as generic power domain.\n");
-- 
2.1.4




More information about the linux-arm-kernel mailing list