diff options
-rw-r--r-- | drivers/pmdomain/Makefile | 1 | ||||
-rw-r--r-- | drivers/pmdomain/rpm_test.c | 293 |
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"); |