1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com> |
4 | * |
5 | * Based on panfrost_devfreq.c: |
6 | * Copyright 2019 Collabora ltd. |
7 | */ |
8 | #include <linux/clk.h> |
9 | #include <linux/devfreq.h> |
10 | #include <linux/devfreq_cooling.h> |
11 | #include <linux/device.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/pm_opp.h> |
14 | #include <linux/property.h> |
15 | |
16 | #include "lima_device.h" |
17 | #include "lima_devfreq.h" |
18 | |
19 | static void lima_devfreq_update_utilization(struct lima_devfreq *devfreq) |
20 | { |
21 | ktime_t now, last; |
22 | |
23 | now = ktime_get(); |
24 | last = devfreq->time_last_update; |
25 | |
26 | if (devfreq->busy_count > 0) |
27 | devfreq->busy_time += ktime_sub(now, last); |
28 | else |
29 | devfreq->idle_time += ktime_sub(now, last); |
30 | |
31 | devfreq->time_last_update = now; |
32 | } |
33 | |
34 | static int lima_devfreq_target(struct device *dev, unsigned long *freq, |
35 | u32 flags) |
36 | { |
37 | struct dev_pm_opp *opp; |
38 | |
39 | opp = devfreq_recommended_opp(dev, freq, flags); |
40 | if (IS_ERR(ptr: opp)) |
41 | return PTR_ERR(ptr: opp); |
42 | dev_pm_opp_put(opp); |
43 | |
44 | return dev_pm_opp_set_rate(dev, target_freq: *freq); |
45 | } |
46 | |
47 | static void lima_devfreq_reset(struct lima_devfreq *devfreq) |
48 | { |
49 | devfreq->busy_time = 0; |
50 | devfreq->idle_time = 0; |
51 | devfreq->time_last_update = ktime_get(); |
52 | } |
53 | |
54 | static int lima_devfreq_get_dev_status(struct device *dev, |
55 | struct devfreq_dev_status *status) |
56 | { |
57 | struct lima_device *ldev = dev_get_drvdata(dev); |
58 | struct lima_devfreq *devfreq = &ldev->devfreq; |
59 | unsigned long irqflags; |
60 | |
61 | status->current_frequency = clk_get_rate(clk: ldev->clk_gpu); |
62 | |
63 | spin_lock_irqsave(&devfreq->lock, irqflags); |
64 | |
65 | lima_devfreq_update_utilization(devfreq); |
66 | |
67 | status->total_time = ktime_to_ns(ktime_add(devfreq->busy_time, |
68 | devfreq->idle_time)); |
69 | status->busy_time = ktime_to_ns(kt: devfreq->busy_time); |
70 | |
71 | lima_devfreq_reset(devfreq); |
72 | |
73 | spin_unlock_irqrestore(lock: &devfreq->lock, flags: irqflags); |
74 | |
75 | dev_dbg(ldev->dev, "busy %lu total %lu %lu %% freq %lu MHz\n" , |
76 | status->busy_time, status->total_time, |
77 | status->busy_time / (status->total_time / 100), |
78 | status->current_frequency / 1000 / 1000); |
79 | |
80 | return 0; |
81 | } |
82 | |
83 | static struct devfreq_dev_profile lima_devfreq_profile = { |
84 | .timer = DEVFREQ_TIMER_DELAYED, |
85 | .polling_ms = 50, /* ~3 frames */ |
86 | .target = lima_devfreq_target, |
87 | .get_dev_status = lima_devfreq_get_dev_status, |
88 | }; |
89 | |
90 | void lima_devfreq_fini(struct lima_device *ldev) |
91 | { |
92 | struct lima_devfreq *devfreq = &ldev->devfreq; |
93 | |
94 | if (devfreq->cooling) { |
95 | devfreq_cooling_unregister(dfc: devfreq->cooling); |
96 | devfreq->cooling = NULL; |
97 | } |
98 | |
99 | if (devfreq->devfreq) { |
100 | devm_devfreq_remove_device(dev: ldev->dev, devfreq: devfreq->devfreq); |
101 | devfreq->devfreq = NULL; |
102 | } |
103 | } |
104 | |
105 | int lima_devfreq_init(struct lima_device *ldev) |
106 | { |
107 | struct thermal_cooling_device *cooling; |
108 | struct device *dev = ldev->dev; |
109 | struct devfreq *devfreq; |
110 | struct lima_devfreq *ldevfreq = &ldev->devfreq; |
111 | struct dev_pm_opp *opp; |
112 | unsigned long cur_freq; |
113 | int ret; |
114 | const char *regulator_names[] = { "mali" , NULL }; |
115 | |
116 | if (!device_property_present(dev, propname: "operating-points-v2" )) |
117 | /* Optional, continue without devfreq */ |
118 | return 0; |
119 | |
120 | spin_lock_init(&ldevfreq->lock); |
121 | |
122 | /* |
123 | * clkname is set separately so it is not affected by the optional |
124 | * regulator setting which may return error. |
125 | */ |
126 | ret = devm_pm_opp_set_clkname(dev, name: "core" ); |
127 | if (ret) |
128 | return ret; |
129 | |
130 | ret = devm_pm_opp_set_regulators(dev, names: regulator_names); |
131 | if (ret) { |
132 | /* Continue if the optional regulator is missing */ |
133 | if (ret != -ENODEV) |
134 | return ret; |
135 | } |
136 | |
137 | ret = devm_pm_opp_of_add_table(dev); |
138 | if (ret) |
139 | return ret; |
140 | |
141 | lima_devfreq_reset(devfreq: ldevfreq); |
142 | |
143 | cur_freq = clk_get_rate(clk: ldev->clk_gpu); |
144 | |
145 | opp = devfreq_recommended_opp(dev, freq: &cur_freq, flags: 0); |
146 | if (IS_ERR(ptr: opp)) |
147 | return PTR_ERR(ptr: opp); |
148 | |
149 | lima_devfreq_profile.initial_freq = cur_freq; |
150 | dev_pm_opp_put(opp); |
151 | |
152 | /* |
153 | * Setup default thresholds for the simple_ondemand governor. |
154 | * The values are chosen based on experiments. |
155 | */ |
156 | ldevfreq->gov_data.upthreshold = 30; |
157 | ldevfreq->gov_data.downdifferential = 5; |
158 | |
159 | devfreq = devm_devfreq_add_device(dev, profile: &lima_devfreq_profile, |
160 | DEVFREQ_GOV_SIMPLE_ONDEMAND, |
161 | data: &ldevfreq->gov_data); |
162 | if (IS_ERR(ptr: devfreq)) { |
163 | dev_err(dev, "Couldn't initialize GPU devfreq\n" ); |
164 | return PTR_ERR(ptr: devfreq); |
165 | } |
166 | |
167 | ldevfreq->devfreq = devfreq; |
168 | |
169 | cooling = of_devfreq_cooling_register(np: dev->of_node, df: devfreq); |
170 | if (IS_ERR(ptr: cooling)) |
171 | dev_info(dev, "Failed to register cooling device\n" ); |
172 | else |
173 | ldevfreq->cooling = cooling; |
174 | |
175 | return 0; |
176 | } |
177 | |
178 | void lima_devfreq_record_busy(struct lima_devfreq *devfreq) |
179 | { |
180 | unsigned long irqflags; |
181 | |
182 | if (!devfreq->devfreq) |
183 | return; |
184 | |
185 | spin_lock_irqsave(&devfreq->lock, irqflags); |
186 | |
187 | lima_devfreq_update_utilization(devfreq); |
188 | |
189 | devfreq->busy_count++; |
190 | |
191 | spin_unlock_irqrestore(lock: &devfreq->lock, flags: irqflags); |
192 | } |
193 | |
194 | void lima_devfreq_record_idle(struct lima_devfreq *devfreq) |
195 | { |
196 | unsigned long irqflags; |
197 | |
198 | if (!devfreq->devfreq) |
199 | return; |
200 | |
201 | spin_lock_irqsave(&devfreq->lock, irqflags); |
202 | |
203 | lima_devfreq_update_utilization(devfreq); |
204 | |
205 | WARN_ON(--devfreq->busy_count < 0); |
206 | |
207 | spin_unlock_irqrestore(lock: &devfreq->lock, flags: irqflags); |
208 | } |
209 | |
210 | int lima_devfreq_resume(struct lima_devfreq *devfreq) |
211 | { |
212 | unsigned long irqflags; |
213 | |
214 | if (!devfreq->devfreq) |
215 | return 0; |
216 | |
217 | spin_lock_irqsave(&devfreq->lock, irqflags); |
218 | |
219 | lima_devfreq_reset(devfreq); |
220 | |
221 | spin_unlock_irqrestore(lock: &devfreq->lock, flags: irqflags); |
222 | |
223 | return devfreq_resume_device(devfreq: devfreq->devfreq); |
224 | } |
225 | |
226 | int lima_devfreq_suspend(struct lima_devfreq *devfreq) |
227 | { |
228 | if (!devfreq->devfreq) |
229 | return 0; |
230 | |
231 | return devfreq_suspend_device(devfreq: devfreq->devfreq); |
232 | } |
233 | |