Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * PM domains for CPUs via genpd - managed by cpuidle-psci.
0004  *
0005  * Copyright (C) 2019 Linaro Ltd.
0006  * Author: Ulf Hansson <ulf.hansson@linaro.org>
0007  *
0008  */
0009 
0010 #define pr_fmt(fmt) "CPUidle PSCI: " fmt
0011 
0012 #include <linux/cpu.h>
0013 #include <linux/device.h>
0014 #include <linux/kernel.h>
0015 #include <linux/platform_device.h>
0016 #include <linux/pm_domain.h>
0017 #include <linux/pm_runtime.h>
0018 #include <linux/psci.h>
0019 #include <linux/slab.h>
0020 #include <linux/string.h>
0021 
0022 #include "cpuidle-psci.h"
0023 
0024 struct psci_pd_provider {
0025     struct list_head link;
0026     struct device_node *node;
0027 };
0028 
0029 static LIST_HEAD(psci_pd_providers);
0030 static bool psci_pd_allow_domain_state;
0031 
0032 static int psci_pd_power_off(struct generic_pm_domain *pd)
0033 {
0034     struct genpd_power_state *state = &pd->states[pd->state_idx];
0035     u32 *pd_state;
0036 
0037     if (!state->data)
0038         return 0;
0039 
0040     if (!psci_pd_allow_domain_state)
0041         return -EBUSY;
0042 
0043     /* OSI mode is enabled, set the corresponding domain state. */
0044     pd_state = state->data;
0045     psci_set_domain_state(*pd_state);
0046 
0047     return 0;
0048 }
0049 
0050 static int psci_pd_init(struct device_node *np, bool use_osi)
0051 {
0052     struct generic_pm_domain *pd;
0053     struct psci_pd_provider *pd_provider;
0054     struct dev_power_governor *pd_gov;
0055     int ret = -ENOMEM;
0056 
0057     pd = dt_idle_pd_alloc(np, psci_dt_parse_state_node);
0058     if (!pd)
0059         goto out;
0060 
0061     pd_provider = kzalloc(sizeof(*pd_provider), GFP_KERNEL);
0062     if (!pd_provider)
0063         goto free_pd;
0064 
0065     pd->flags |= GENPD_FLAG_IRQ_SAFE | GENPD_FLAG_CPU_DOMAIN;
0066 
0067     /* Allow power off when OSI has been successfully enabled. */
0068     if (use_osi)
0069         pd->power_off = psci_pd_power_off;
0070     else
0071         pd->flags |= GENPD_FLAG_ALWAYS_ON;
0072 
0073     /* Use governor for CPU PM domains if it has some states to manage. */
0074     pd_gov = pd->states ? &pm_domain_cpu_gov : NULL;
0075 
0076     ret = pm_genpd_init(pd, pd_gov, false);
0077     if (ret)
0078         goto free_pd_prov;
0079 
0080     ret = of_genpd_add_provider_simple(np, pd);
0081     if (ret)
0082         goto remove_pd;
0083 
0084     pd_provider->node = of_node_get(np);
0085     list_add(&pd_provider->link, &psci_pd_providers);
0086 
0087     pr_debug("init PM domain %s\n", pd->name);
0088     return 0;
0089 
0090 remove_pd:
0091     pm_genpd_remove(pd);
0092 free_pd_prov:
0093     kfree(pd_provider);
0094 free_pd:
0095     dt_idle_pd_free(pd);
0096 out:
0097     pr_err("failed to init PM domain ret=%d %pOF\n", ret, np);
0098     return ret;
0099 }
0100 
0101 static void psci_pd_remove(void)
0102 {
0103     struct psci_pd_provider *pd_provider, *it;
0104     struct generic_pm_domain *genpd;
0105 
0106     list_for_each_entry_safe(pd_provider, it, &psci_pd_providers, link) {
0107         of_genpd_del_provider(pd_provider->node);
0108 
0109         genpd = of_genpd_remove_last(pd_provider->node);
0110         if (!IS_ERR(genpd))
0111             kfree(genpd);
0112 
0113         of_node_put(pd_provider->node);
0114         list_del(&pd_provider->link);
0115         kfree(pd_provider);
0116     }
0117 }
0118 
0119 static bool psci_pd_try_set_osi_mode(void)
0120 {
0121     int ret;
0122 
0123     if (!psci_has_osi_support())
0124         return false;
0125 
0126     ret = psci_set_osi_mode(true);
0127     if (ret) {
0128         pr_warn("failed to enable OSI mode: %d\n", ret);
0129         return false;
0130     }
0131 
0132     return true;
0133 }
0134 
0135 static void psci_cpuidle_domain_sync_state(struct device *dev)
0136 {
0137     /*
0138      * All devices have now been attached/probed to the PM domain topology,
0139      * hence it's fine to allow domain states to be picked.
0140      */
0141     psci_pd_allow_domain_state = true;
0142 }
0143 
0144 static const struct of_device_id psci_of_match[] = {
0145     { .compatible = "arm,psci-1.0" },
0146     {}
0147 };
0148 
0149 static int psci_cpuidle_domain_probe(struct platform_device *pdev)
0150 {
0151     struct device_node *np = pdev->dev.of_node;
0152     struct device_node *node;
0153     bool use_osi;
0154     int ret = 0, pd_count = 0;
0155 
0156     if (!np)
0157         return -ENODEV;
0158 
0159     /* If OSI mode is supported, let's try to enable it. */
0160     use_osi = psci_pd_try_set_osi_mode();
0161 
0162     /*
0163      * Parse child nodes for the "#power-domain-cells" property and
0164      * initialize a genpd/genpd-of-provider pair when it's found.
0165      */
0166     for_each_child_of_node(np, node) {
0167         if (!of_find_property(node, "#power-domain-cells", NULL))
0168             continue;
0169 
0170         ret = psci_pd_init(node, use_osi);
0171         if (ret)
0172             goto put_node;
0173 
0174         pd_count++;
0175     }
0176 
0177     /* Bail out if not using the hierarchical CPU topology. */
0178     if (!pd_count)
0179         goto no_pd;
0180 
0181     /* Link genpd masters/subdomains to model the CPU topology. */
0182     ret = dt_idle_pd_init_topology(np);
0183     if (ret)
0184         goto remove_pd;
0185 
0186     pr_info("Initialized CPU PM domain topology\n");
0187     return 0;
0188 
0189 put_node:
0190     of_node_put(node);
0191 remove_pd:
0192     psci_pd_remove();
0193     pr_err("failed to create CPU PM domains ret=%d\n", ret);
0194 no_pd:
0195     if (use_osi)
0196         psci_set_osi_mode(false);
0197     return ret;
0198 }
0199 
0200 static struct platform_driver psci_cpuidle_domain_driver = {
0201     .probe  = psci_cpuidle_domain_probe,
0202     .driver = {
0203         .name = "psci-cpuidle-domain",
0204         .of_match_table = psci_of_match,
0205         .sync_state = psci_cpuidle_domain_sync_state,
0206     },
0207 };
0208 
0209 static int __init psci_idle_init_domains(void)
0210 {
0211     return platform_driver_register(&psci_cpuidle_domain_driver);
0212 }
0213 subsys_initcall(psci_idle_init_domains);