aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Hansson <ulf.hansson@linaro.org>2016-07-20 12:04:13 +0200
committerUlf Hansson <ulf.hansson@linaro.org>2024-01-23 23:35:00 +0100
commit5c1c472cfc5220834212519a60b912b892f0ecc5 (patch)
treeeab9ba6d7ef10eab07d5eb3ce2ee47542971e969
parent94b7323ad6ecdf5c1f3a785920414f22fa6a0c6e (diff)
pmdomain: Add test driver for power management
Extended with debugfs for performance states. Extended to deal with OPP and multiple PM domains attaching. Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
-rw-r--r--drivers/pmdomain/Makefile1
-rw-r--r--drivers/pmdomain/rpm_test.c293
2 files changed, 294 insertions, 0 deletions
diff --git a/drivers/pmdomain/Makefile b/drivers/pmdomain/Makefile
index a68ece2f4c68..81ef64d277e1 100644
--- a/drivers/pmdomain/Makefile
+++ b/drivers/pmdomain/Makefile
@@ -17,3 +17,4 @@ obj-y += tegra/
obj-y += ti/
obj-y += xilinx/
obj-y += core.o governor.o
+obj-y += rpm_test.o
diff --git a/drivers/pmdomain/rpm_test.c b/drivers/pmdomain/rpm_test.c
new file mode 100644
index 000000000000..c65eec9969c2
--- /dev/null
+++ b/drivers/pmdomain/rpm_test.c
@@ -0,0 +1,293 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Linaro Ltd
+ *
+ * Author: Ulf Hansson <ulf.hansson@linaro.org>
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/printk.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
+
+struct rpm_test_dev {
+ struct device *dev;
+ /* add more if needed */
+ int opp_token;
+ struct dev_pm_domain_list *pds;
+ struct dentry *debugfs_root;
+ u32 perf_state;
+};
+
+static const char *domain_names6[] = { "perf0", "perf1", NULL };
+
+static int rpm_test_perf_get(void *data, u64 *val)
+{
+ struct rpm_test_dev *rdev = data;
+
+ *val = rdev->perf_state;
+
+ return 0;
+}
+
+static int rpm_test_perf_set(void *data, u64 val)
+{
+ struct rpm_test_dev *rdev = data;
+ int ret;
+
+ ret = dev_pm_domain_set_performance_state(rdev->dev, val);
+ if (ret)
+ return ret;
+
+ rdev->perf_state = val;
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(rpm_test_perf_ops, rpm_test_perf_get,
+ rpm_test_perf_set, "%llu\n");
+
+static int rpm_test_probe(struct platform_device *pdev)
+{
+ struct rpm_test_dev *rdev;
+ struct dev_pm_opp *opp;
+ int ret;
+
+ dev_info(&pdev->dev, "%s\n", __func__);
+
+ rdev = devm_kzalloc(&pdev->dev, sizeof(*rdev), GFP_KERNEL);
+ if (!rdev)
+ return -ENOMEM;
+
+ /* SCMI perf */
+ if (strcmp(dev_name(&pdev->dev), "rpm_test3") == 0) {
+ opp = dev_pm_opp_find_level_exact(&pdev->dev, 450000);
+ if (IS_ERR(opp))
+ dev_info(&pdev->dev, "OPP not found\n");
+ else
+ dev_pm_opp_set_opp(&pdev->dev, opp);
+ }
+
+ /* SCMI perf */
+ if (strcmp(dev_name(&pdev->dev), "rpm_test4") == 0) {
+ opp = dev_pm_opp_find_level_exact(&pdev->dev, 600000);
+ if (IS_ERR(opp))
+ dev_info(&pdev->dev, "OPP not found\n");
+ else
+ dev_pm_opp_set_opp(&pdev->dev, opp);
+ }
+
+ /* Single PM domain - OPP */
+ if (strcmp(dev_name(&pdev->dev), "rpm_test5") == 0) {
+ /*
+ * Calling devm_pm_opp_of_add_table() twice works fine. Cleanup?
+ * _read_opp_key() fails if the primary opp-node lacks any of
+ * opp-level, opp-hz, opp-peak-kBps/opp-avg-kBps.
+ * Get lazy link WARN when opp-level is both in primary node and
+ * in the required-opps node. Needs to be fixed!
+ */
+ ret = devm_pm_opp_of_add_table(&pdev->dev);
+ if (ret)
+ dev_info(&pdev->dev, "Unable to add OPP table %d\n", ret);
+
+ /*
+ * Works when level is located in the required-opps and no level
+ * in the primary node. If no required-opps but level in primary
+ * node, then the level will set be, which may not be supported
+ * by the genpd. The latter is still useful when the genpd has
+ * created the OPPs dynamically via GENPD_FLAG_OPP_TABLE_FW.
+ */
+// opp = dev_pm_opp_find_level_exact(&pdev->dev, 100);
+
+ /*
+ * Works when opp-hz is in the primary opp-node and level in the
+ * required-opps for the single PM domain case.
+ * Is this true for the multi case too? See rpm_test6.
+ */
+ opp = dev_pm_opp_find_freq_exact(&pdev->dev, 133, true);
+ if (IS_ERR(opp))
+ dev_info(&pdev->dev, "OPP not found\n");
+ else
+ dev_pm_opp_set_opp(&pdev->dev, opp);
+
+ /*
+ * Q: If there is no rate, level or bandwidth to specify for an
+ * OPP, but only a required-opps - should we allow a "label"
+ * to be used instead?
+ */
+ }
+
+ /* Multi PM domain - OPP-legacy. */
+ if (strcmp(dev_name(&pdev->dev), "rpm_test6") == 0) {
+ struct device **virt_devs = NULL;
+ struct dev_pm_opp_config config = {
+ .genpd_names = domain_names6,
+ //.virt_devs = NULL, //NULL means no assignment is done
+ .virt_devs = &virt_devs,
+ };
+
+ ret = dev_pm_opp_set_config(&pdev->dev, &config);
+ if (ret < 0) {
+ dev_info(&pdev->dev, "Unable to attach and set OPP config %d\n", ret);
+ goto out;
+ }
+
+ rdev->opp_token = ret;
+
+ ret = devm_pm_opp_of_add_table(&pdev->dev);
+ if (ret)
+ dev_info(&pdev->dev, "Unable to add OPP table %d\n", ret);
+
+ ret = dev_pm_opp_get_opp_count(&pdev->dev);
+ dev_info(&pdev->dev, "Num OPP %d\n", ret);
+
+ /*
+ * Finding the level from one of the required-opps doesn't work,
+ * which works fine for the single PM domain case.
+ */
+ //opp = dev_pm_opp_find_level_exact(&pdev->dev, 100);
+
+ /*
+ * Finding the opp from a primary node specifying the opp-hz
+ * works.
+ */
+ opp = dev_pm_opp_find_freq_exact(&pdev->dev, 266, true);
+ if (IS_ERR(opp))
+ dev_info(&pdev->dev, "OPP not found\n");
+ else
+ dev_pm_opp_set_opp(&pdev->dev, opp);
+ }
+
+ /* Multi PM domain in DT - no perf/OPP. */
+ if (strcmp(dev_name(&pdev->dev), "rpm_test8") == 0) {
+ //ret = dev_pm_domain_attach_list(&pdev->dev, &attach_data8,
+ ret = dev_pm_domain_attach_list(&pdev->dev, NULL,
+ &rdev->pds);
+ dev_info(&pdev->dev, "ADDED multiple PM domains ret=%d\n", ret);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Debugfs for performance states. */
+ rdev->debugfs_root = debugfs_create_dir(dev_name(&pdev->dev), NULL);
+ debugfs_create_file_unsafe("perf_state", S_IRUSR | S_IWUSR,
+ rdev->debugfs_root, rdev,
+ &rpm_test_perf_ops);
+
+ /*
+ * NEXT STEPS:
+ * Single PD with SCMI-perf: Call devm_pm_opp_of_add_table() even if it
+ * isn'needed?
+ * Multi PD with one scmi-perf and one scmi-power: How can we set an OPP
+ * for the corresponding virtual device to scmi-perf, when operating on
+ * &pdev->dev? Maybe instead of calling dev_pm_opp_set_config() with
+ * genpd-names, use it to set the required/virt-devs for the OPP table?
+ */
+out:
+
+ rdev->dev = &pdev->dev;
+ platform_set_drvdata(pdev, rdev);
+
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 100);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ pm_runtime_mark_last_busy(&pdev->dev);
+ pm_runtime_put(&pdev->dev);
+
+ dev_info(&pdev->dev, "%s - DONE\n", __func__);
+ return 0;
+}
+
+static int rpm_test_remove(struct platform_device *pdev)
+{
+ struct rpm_test_dev *rdev = platform_get_drvdata(pdev);
+
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+
+ dev_pm_domain_detach_list(rdev->pds);
+ debugfs_remove_recursive(rdev->debugfs_root);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int rpm_test_suspend(struct device *dev)
+{
+ int ret;
+
+ dev_info(dev, "%s\n", __func__);
+ pm_runtime_resume(dev);
+ ret = pm_runtime_force_suspend(dev);
+ dev_info(dev, "%s ret=%d\n", __func__, ret);
+
+ return ret;
+}
+
+static int rpm_test_resume(struct device *dev)
+{
+ int ret;
+
+ dev_info(dev, "%s\n", __func__);
+ ret = pm_runtime_force_resume(dev);
+ dev_info(dev, "%s ret=%d\n", __func__, ret);
+
+ return ret;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int rpm_test_runtime_suspend(struct device *dev)
+{
+ dev_info(dev, "%s\n", __func__);
+ return 0;
+}
+
+static int rpm_test_runtime_resume(struct device *dev)
+{
+ dev_info(dev, "%s\n", __func__);
+ return 0;
+}
+#endif
+
+static const struct of_device_id rpm_test_ids[] = {
+ {
+ .compatible = "test,runtime-pm-test",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rpm_test_ids);
+
+static const struct dev_pm_ops rpm_test_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(rpm_test_suspend, rpm_test_resume)
+ SET_RUNTIME_PM_OPS(rpm_test_runtime_suspend,
+ rpm_test_runtime_resume,
+ NULL)
+};
+
+static struct platform_driver rpm_test_driver = {
+ .probe = rpm_test_probe,
+ .remove = rpm_test_remove,
+ .driver = {
+ .name = "rpm-test-drv",
+ .pm = &rpm_test_dev_pm_ops,
+ .of_match_table = rpm_test_ids,
+ },
+};
+
+module_platform_driver(rpm_test_driver);
+MODULE_LICENSE("GPL v2");