diff options
author | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2019-09-17 03:45:44 -0400 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2019-09-17 03:45:44 -0400 |
commit | 031f469ecf7c85d2d43a04abd256b6f3a1ef5794 (patch) | |
tree | cf02ceeaab2e2c9725aad129e795048cc63c4eb5 /drivers/devfreq/tegra20-devfreq.c | |
parent | ca61a72ac371b5a12fafd36248b93431c6694c3c (diff) | |
parent | da9cd91c87adb27594357a203b65025dcd32e266 (diff) |
Merge branch 'pm-devfreq'
* pm-devfreq: (28 commits)
PM / devfreq: passive: fix compiler warning
PM / devfreq: passive: Use non-devm notifiers
PM / devfreq: exynos-bus: Convert to use dev_pm_opp_set_rate()
PM / devfreq: exynos-bus: Correct clock enable sequence
PM / devfreq: Correct devm_devfreq_remove_device() documentation
PM / devfreq: events: extend events by type of counted data
PM / devfreq: exynos-events: change matching code during probe
PM / devfreq: tegra20: add COMMON_CLK dependency
PM / devfreq: events: add Exynos PPMU new events
PM / devfreq: Fix kernel oops on governor module load
PM / devfreq: rk3399_dmc: Fix spelling typo
PM / devfreq: Fix spelling typo
PM / devfreq: Introduce driver for NVIDIA Tegra20
PM / devfreq: tegra: Rename tegra-devfreq.c to tegra30-devfreq.c
PM / devfreq: tegra: Enable COMPILE_TEST for the driver
PM / devfreq: tegra: Support Tegra30
PM / devfreq: tegra: Reconfigure hardware on governor's restart
PM / devfreq: tegra: Move governor registration to driver's probe
PM / devfreq: tegra: Mark ACTMON's governor as immutable
PM / devfreq: tegra: Avoid inconsistency of current frequency value
...
Diffstat (limited to 'drivers/devfreq/tegra20-devfreq.c')
-rw-r--r-- | drivers/devfreq/tegra20-devfreq.c | 212 |
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 | |||
31 | struct tegra_devfreq { | ||
32 | struct devfreq *devfreq; | ||
33 | struct clk *emc_clock; | ||
34 | void __iomem *regs; | ||
35 | }; | ||
36 | |||
37 | static 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 | |||
63 | restore_min_rate: | ||
64 | clk_set_min_rate(tegra->emc_clock, devfreq->previous_freq); | ||
65 | |||
66 | return err; | ||
67 | } | ||
68 | |||
69 | static 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 | |||
94 | static 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 | |||
100 | static 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 | |||
122 | static 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 | |||
184 | remove_opps: | ||
185 | dev_pm_opp_remove_all_dynamic(&pdev->dev); | ||
186 | |||
187 | return err; | ||
188 | } | ||
189 | |||
190 | static 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 | |||
200 | static struct platform_driver tegra_devfreq_driver = { | ||
201 | .probe = tegra_devfreq_probe, | ||
202 | .remove = tegra_devfreq_remove, | ||
203 | .driver = { | ||
204 | .name = "tegra20-devfreq", | ||
205 | }, | ||
206 | }; | ||
207 | module_platform_driver(tegra_devfreq_driver); | ||
208 | |||
209 | MODULE_ALIAS("platform:tegra20-devfreq"); | ||
210 | MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>"); | ||
211 | MODULE_DESCRIPTION("NVIDIA Tegra20 devfreq driver"); | ||
212 | MODULE_LICENSE("GPL v2"); | ||