summaryrefslogtreecommitdiffstats
path: root/drivers/devfreq/tegra20-devfreq.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2019-09-17 22:15:14 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2019-09-17 22:15:14 -0400
commit77dcfe2b9edc98286cf18e03c243c9b999f955d9 (patch)
tree0ba3c4002b6c26c715bf03fac81d63de13c01d96 /drivers/devfreq/tegra20-devfreq.c
parent04cbfba6208592999d7bfe6609ec01dc3fde73f5 (diff)
parentfc6763a2d7e0a7f49ccec97a46e92e9fb1f3f9dd (diff)
Merge tag 'pm-5.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm
Pull power management updates from Rafael Wysocki: "These include a rework of the main suspend-to-idle code flow (related to the handling of spurious wakeups), a switch over of several users of cpufreq notifiers to QoS-based limits, a new devfreq driver for Tegra20, a new cpuidle driver and governor for virtualized guests, an extension of the wakeup sources framework to expose wakeup sources as device objects in sysfs, and more. Specifics: - Rework the main suspend-to-idle control flow to avoid repeating "noirq" device resume and suspend operations in case of spurious wakeups from the ACPI EC and decouple the ACPI EC wakeups support from the LPS0 _DSM support (Rafael Wysocki). - Extend the wakeup sources framework to expose wakeup sources as device objects in sysfs (Tri Vo, Stephen Boyd). - Expose system suspend statistics in sysfs (Kalesh Singh). - Introduce a new haltpoll cpuidle driver and a new matching governor for virtualized guests wanting to do guest-side polling in the idle loop (Marcelo Tosatti, Joao Martins, Wanpeng Li, Stephen Rothwell). - Fix the menu and teo cpuidle governors to allow the scheduler tick to be stopped if PM QoS is used to limit the CPU idle state exit latency in some cases (Rafael Wysocki). - Increase the resolution of the play_idle() argument to microseconds for more fine-grained injection of CPU idle cycles (Daniel Lezcano). - Switch over some users of cpuidle notifiers to the new QoS-based frequency limits and drop the CPUFREQ_ADJUST and CPUFREQ_NOTIFY policy notifier events (Viresh Kumar). - Add new cpufreq driver based on nvmem for sun50i (Yangtao Li). - Add support for MT8183 and MT8516 to the mediatek cpufreq driver (Andrew-sh.Cheng, Fabien Parent). - Add i.MX8MN support to the imx-cpufreq-dt cpufreq driver (Anson Huang). - Add qcs404 to cpufreq-dt-platdev blacklist (Jorge Ramirez-Ortiz). - Update the qcom cpufreq driver (among other things, to make it easier to extend and to use kryo cpufreq for other nvmem-based SoCs) and add qcs404 support to it (Niklas Cassel, Douglas RAILLARD, Sibi Sankar, Sricharan R). - Fix assorted issues and make assorted minor improvements in the cpufreq code (Colin Ian King, Douglas RAILLARD, Florian Fainelli, Gustavo Silva, Hariprasad Kelam). - Add new devfreq driver for NVidia Tegra20 (Dmitry Osipenko, Arnd Bergmann). - Add new Exynos PPMU events to devfreq events and extend that mechanism (Lukasz Luba). - Fix and clean up the exynos-bus devfreq driver (Kamil Konieczny). - Improve devfreq documentation and governor code, fix spelling typos in devfreq (Ezequiel Garcia, Krzysztof Kozlowski, Leonard Crestez, MyungJoo Ham, Gaël PORTAY). - Add regulators enable and disable to the OPP (operating performance points) framework (Kamil Konieczny). - Update the OPP framework to support multiple opp-suspend properties (Anson Huang). - Fix assorted issues and make assorted minor improvements in the OPP code (Niklas Cassel, Viresh Kumar, Yue Hu). - Clean up the generic power domains (genpd) framework (Ulf Hansson). - Clean up assorted pieces of power management code and documentation (Akinobu Mita, Amit Kucheria, Chuhong Yuan). - Update the pm-graph tool to version 5.5 including multiple fixes and improvements (Todd Brandt). - Update the cpupower utility (Benjamin Weis, Geert Uytterhoeven, Sébastien Szymanski)" * tag 'pm-5.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (126 commits) cpuidle-haltpoll: Enable kvm guest polling when dedicated physical CPUs are available cpuidle-haltpoll: do not set an owner to allow modunload cpuidle-haltpoll: return -ENODEV on modinit failure cpuidle-haltpoll: set haltpoll as preferred governor cpuidle: allow governor switch on cpuidle_register_driver() PM: runtime: Documentation: add runtime_status ABI document pm-graph: make setVal unbuffered again for python2 and python3 powercap: idle_inject: Use higher resolution for idle injection cpuidle: play_idle: Increase the resolution to usec cpuidle-haltpoll: vcpu hotplug support cpufreq: Add qcs404 to cpufreq-dt-platdev blacklist cpufreq: qcom: Add support for qcs404 on nvmem driver cpufreq: qcom: Refactor the driver to make it easier to extend cpufreq: qcom: Re-organise kryo cpufreq to use it for other nvmem based qcom socs dt-bindings: opp: Add qcom-opp bindings with properties needed for CPR dt-bindings: opp: qcom-nvmem: Support pstates provided by a power domain Documentation: cpufreq: Update policy notifier documentation cpufreq: Remove CPUFREQ_ADJUST and CPUFREQ_NOTIFY policy notifier events PM / Domains: Verify PM domain type in dev_pm_genpd_set_performance_state() PM / Domains: Simplify genpd_lookup_dev() ...
Diffstat (limited to 'drivers/devfreq/tegra20-devfreq.c')
-rw-r--r--drivers/devfreq/tegra20-devfreq.c212
1 files changed, 212 insertions, 0 deletions
diff --git a/drivers/devfreq/tegra20-devfreq.c b/drivers/devfreq/tegra20-devfreq.c
new file mode 100644
index 000000000000..ff82bac9ee4e
--- /dev/null
+++ b/drivers/devfreq/tegra20-devfreq.c
@@ -0,0 +1,212 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * NVIDIA Tegra20 devfreq driver
4 *
5 * Copyright (C) 2019 GRATE-DRIVER project
6 */
7
8#include <linux/clk.h>
9#include <linux/devfreq.h>
10#include <linux/io.h>
11#include <linux/kernel.h>
12#include <linux/module.h>
13#include <linux/of_device.h>
14#include <linux/platform_device.h>
15#include <linux/pm_opp.h>
16#include <linux/slab.h>
17
18#include <soc/tegra/mc.h>
19
20#include "governor.h"
21
22#define MC_STAT_CONTROL 0x90
23#define MC_STAT_EMC_CLOCK_LIMIT 0xa0
24#define MC_STAT_EMC_CLOCKS 0xa4
25#define MC_STAT_EMC_CONTROL 0xa8
26#define MC_STAT_EMC_COUNT 0xb8
27
28#define EMC_GATHER_CLEAR (1 << 8)
29#define EMC_GATHER_ENABLE (3 << 8)
30
31struct tegra_devfreq {
32 struct devfreq *devfreq;
33 struct clk *emc_clock;
34 void __iomem *regs;
35};
36
37static int tegra_devfreq_target(struct device *dev, unsigned long *freq,
38 u32 flags)
39{
40 struct tegra_devfreq *tegra = dev_get_drvdata(dev);
41 struct devfreq *devfreq = tegra->devfreq;
42 struct dev_pm_opp *opp;
43 unsigned long rate;
44 int err;
45
46 opp = devfreq_recommended_opp(dev, freq, flags);
47 if (IS_ERR(opp))
48 return PTR_ERR(opp);
49
50 rate = dev_pm_opp_get_freq(opp);
51 dev_pm_opp_put(opp);
52
53 err = clk_set_min_rate(tegra->emc_clock, rate);
54 if (err)
55 return err;
56
57 err = clk_set_rate(tegra->emc_clock, 0);
58 if (err)
59 goto restore_min_rate;
60
61 return 0;
62
63restore_min_rate:
64 clk_set_min_rate(tegra->emc_clock, devfreq->previous_freq);
65
66 return err;
67}
68
69static int tegra_devfreq_get_dev_status(struct device *dev,
70 struct devfreq_dev_status *stat)
71{
72 struct tegra_devfreq *tegra = dev_get_drvdata(dev);
73
74 /*
75 * EMC_COUNT returns number of memory events, that number is lower
76 * than the number of clocks. Conversion ratio of 1/8 results in a
77 * bit higher bandwidth than actually needed, it is good enough for
78 * the time being because drivers don't support requesting minimum
79 * needed memory bandwidth yet.
80 *
81 * TODO: adjust the ratio value once relevant drivers will support
82 * memory bandwidth management.
83 */
84 stat->busy_time = readl_relaxed(tegra->regs + MC_STAT_EMC_COUNT);
85 stat->total_time = readl_relaxed(tegra->regs + MC_STAT_EMC_CLOCKS) / 8;
86 stat->current_frequency = clk_get_rate(tegra->emc_clock);
87
88 writel_relaxed(EMC_GATHER_CLEAR, tegra->regs + MC_STAT_CONTROL);
89 writel_relaxed(EMC_GATHER_ENABLE, tegra->regs + MC_STAT_CONTROL);
90
91 return 0;
92}
93
94static struct devfreq_dev_profile tegra_devfreq_profile = {
95 .polling_ms = 500,
96 .target = tegra_devfreq_target,
97 .get_dev_status = tegra_devfreq_get_dev_status,
98};
99
100static struct tegra_mc *tegra_get_memory_controller(void)
101{
102 struct platform_device *pdev;
103 struct device_node *np;
104 struct tegra_mc *mc;
105
106 np = of_find_compatible_node(NULL, NULL, "nvidia,tegra20-mc-gart");
107 if (!np)
108 return ERR_PTR(-ENOENT);
109
110 pdev = of_find_device_by_node(np);
111 of_node_put(np);
112 if (!pdev)
113 return ERR_PTR(-ENODEV);
114
115 mc = platform_get_drvdata(pdev);
116 if (!mc)
117 return ERR_PTR(-EPROBE_DEFER);
118
119 return mc;
120}
121
122static int tegra_devfreq_probe(struct platform_device *pdev)
123{
124 struct tegra_devfreq *tegra;
125 struct tegra_mc *mc;
126 unsigned long max_rate;
127 unsigned long rate;
128 int err;
129
130 mc = tegra_get_memory_controller();
131 if (IS_ERR(mc)) {
132 err = PTR_ERR(mc);
133 dev_err(&pdev->dev, "failed to get memory controller: %d\n",
134 err);
135 return err;
136 }
137
138 tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
139 if (!tegra)
140 return -ENOMEM;
141
142 /* EMC is a system-critical clock that is always enabled */
143 tegra->emc_clock = devm_clk_get(&pdev->dev, "emc");
144 if (IS_ERR(tegra->emc_clock)) {
145 err = PTR_ERR(tegra->emc_clock);
146 dev_err(&pdev->dev, "failed to get emc clock: %d\n", err);
147 return err;
148 }
149
150 tegra->regs = mc->regs;
151
152 max_rate = clk_round_rate(tegra->emc_clock, ULONG_MAX);
153
154 for (rate = 0; rate <= max_rate; rate++) {
155 rate = clk_round_rate(tegra->emc_clock, rate);
156
157 err = dev_pm_opp_add(&pdev->dev, rate, 0);
158 if (err) {
159 dev_err(&pdev->dev, "failed to add opp: %d\n", err);
160 goto remove_opps;
161 }
162 }
163
164 /*
165 * Reset statistic gathers state, select global bandwidth for the
166 * statistics collection mode and set clocks counter saturation
167 * limit to maximum.
168 */
169 writel_relaxed(0x00000000, tegra->regs + MC_STAT_CONTROL);
170 writel_relaxed(0x00000000, tegra->regs + MC_STAT_EMC_CONTROL);
171 writel_relaxed(0xffffffff, tegra->regs + MC_STAT_EMC_CLOCK_LIMIT);
172
173 platform_set_drvdata(pdev, tegra);
174
175 tegra->devfreq = devfreq_add_device(&pdev->dev, &tegra_devfreq_profile,
176 DEVFREQ_GOV_SIMPLE_ONDEMAND, NULL);
177 if (IS_ERR(tegra->devfreq)) {
178 err = PTR_ERR(tegra->devfreq);
179 goto remove_opps;
180 }
181
182 return 0;
183
184remove_opps:
185 dev_pm_opp_remove_all_dynamic(&pdev->dev);
186
187 return err;
188}
189
190static int tegra_devfreq_remove(struct platform_device *pdev)
191{
192 struct tegra_devfreq *tegra = platform_get_drvdata(pdev);
193
194 devfreq_remove_device(tegra->devfreq);
195 dev_pm_opp_remove_all_dynamic(&pdev->dev);
196
197 return 0;
198}
199
200static struct platform_driver tegra_devfreq_driver = {
201 .probe = tegra_devfreq_probe,
202 .remove = tegra_devfreq_remove,
203 .driver = {
204 .name = "tegra20-devfreq",
205 },
206};
207module_platform_driver(tegra_devfreq_driver);
208
209MODULE_ALIAS("platform:tegra20-devfreq");
210MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>");
211MODULE_DESCRIPTION("NVIDIA Tegra20 devfreq driver");
212MODULE_LICENSE("GPL v2");