1 | /* $NetBSD: nouveau_subdev_clock_base.c,v 1.2 2015/02/25 17:29:43 riastradh Exp $ */ |
2 | |
3 | /* |
4 | * Copyright 2013 Red Hat Inc. |
5 | * |
6 | * Permission is hereby granted, free of charge, to any person obtaining a |
7 | * copy of this software and associated documentation files (the "Software"), |
8 | * to deal in the Software without restriction, including without limitation |
9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
10 | * and/or sell copies of the Software, and to permit persons to whom the |
11 | * Software is furnished to do so, subject to the following conditions: |
12 | * |
13 | * The above copyright notice and this permission notice shall be included in |
14 | * all copies or substantial portions of the Software. |
15 | * |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
19 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR |
20 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
21 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
22 | * OTHER DEALINGS IN THE SOFTWARE. |
23 | * |
24 | * Authors: Ben Skeggs |
25 | */ |
26 | |
27 | #include <sys/cdefs.h> |
28 | __KERNEL_RCSID(0, "$NetBSD: nouveau_subdev_clock_base.c,v 1.2 2015/02/25 17:29:43 riastradh Exp $" ); |
29 | |
30 | #include <core/option.h> |
31 | |
32 | #include <subdev/clock.h> |
33 | #include <subdev/therm.h> |
34 | #include <subdev/volt.h> |
35 | #include <subdev/fb.h> |
36 | |
37 | #include <subdev/bios.h> |
38 | #include <subdev/bios/boost.h> |
39 | #include <subdev/bios/cstep.h> |
40 | #include <subdev/bios/perf.h> |
41 | |
42 | /****************************************************************************** |
43 | * misc |
44 | *****************************************************************************/ |
45 | static u32 |
46 | nouveau_clock_adjust(struct nouveau_clock *clk, bool adjust, |
47 | u8 pstate, u8 domain, u32 input) |
48 | { |
49 | struct nouveau_bios *bios = nouveau_bios(clk); |
50 | struct nvbios_boostE boostE; |
51 | u8 ver, hdr, cnt, len; |
52 | u16 data; |
53 | |
54 | data = nvbios_boostEm(bios, pstate, &ver, &hdr, &cnt, &len, &boostE); |
55 | if (data) { |
56 | struct nvbios_boostS boostS; |
57 | u8 idx = 0, sver, shdr; |
58 | u16 subd; |
59 | |
60 | input = max(boostE.min, input); |
61 | input = min(boostE.max, input); |
62 | do { |
63 | sver = ver; |
64 | shdr = hdr; |
65 | subd = nvbios_boostSp(bios, idx++, data, &sver, &shdr, |
66 | cnt, len, &boostS); |
67 | if (subd && boostS.domain == domain) { |
68 | if (adjust) |
69 | input = input * boostS.percent / 100; |
70 | input = max(boostS.min, input); |
71 | input = min(boostS.max, input); |
72 | break; |
73 | } |
74 | } while (subd); |
75 | } |
76 | |
77 | return input; |
78 | } |
79 | |
80 | /****************************************************************************** |
81 | * C-States |
82 | *****************************************************************************/ |
83 | static int |
84 | nouveau_cstate_prog(struct nouveau_clock *clk, |
85 | struct nouveau_pstate *pstate, int cstatei) |
86 | { |
87 | struct nouveau_therm *ptherm = nouveau_therm(clk); |
88 | struct nouveau_volt *volt = nouveau_volt(clk); |
89 | struct nouveau_cstate *cstate; |
90 | int ret; |
91 | |
92 | if (!list_empty(&pstate->list)) { |
93 | cstate = list_entry(pstate->list.prev, typeof(*cstate), head); |
94 | } else { |
95 | cstate = &pstate->base; |
96 | } |
97 | |
98 | ret = nouveau_therm_cstate(ptherm, pstate->fanspeed, +1); |
99 | if (ret && ret != -ENODEV) { |
100 | nv_error(clk, "failed to raise fan speed: %d\n" , ret); |
101 | return ret; |
102 | } |
103 | |
104 | ret = volt->set_id(volt, cstate->voltage, +1); |
105 | if (ret && ret != -ENODEV) { |
106 | nv_error(clk, "failed to raise voltage: %d\n" , ret); |
107 | return ret; |
108 | } |
109 | |
110 | ret = clk->calc(clk, cstate); |
111 | if (ret == 0) { |
112 | ret = clk->prog(clk); |
113 | clk->tidy(clk); |
114 | } |
115 | |
116 | ret = volt->set_id(volt, cstate->voltage, -1); |
117 | if (ret && ret != -ENODEV) |
118 | nv_error(clk, "failed to lower voltage: %d\n" , ret); |
119 | |
120 | ret = nouveau_therm_cstate(ptherm, pstate->fanspeed, -1); |
121 | if (ret && ret != -ENODEV) |
122 | nv_error(clk, "failed to lower fan speed: %d\n" , ret); |
123 | |
124 | return 0; |
125 | } |
126 | |
127 | static void |
128 | nouveau_cstate_del(struct nouveau_cstate *cstate) |
129 | { |
130 | list_del(&cstate->head); |
131 | kfree(cstate); |
132 | } |
133 | |
134 | static int |
135 | nouveau_cstate_new(struct nouveau_clock *clk, int idx, |
136 | struct nouveau_pstate *pstate) |
137 | { |
138 | struct nouveau_bios *bios = nouveau_bios(clk); |
139 | struct nouveau_clocks *domain = clk->domains; |
140 | struct nouveau_cstate *cstate = NULL; |
141 | struct nvbios_cstepX cstepX; |
142 | u8 ver, hdr; |
143 | u16 data; |
144 | |
145 | data = nvbios_cstepXp(bios, idx, &ver, &hdr, &cstepX); |
146 | if (!data) |
147 | return -ENOENT; |
148 | |
149 | cstate = kzalloc(sizeof(*cstate), GFP_KERNEL); |
150 | if (!cstate) |
151 | return -ENOMEM; |
152 | |
153 | *cstate = pstate->base; |
154 | cstate->voltage = cstepX.voltage; |
155 | |
156 | while (domain && domain->name != nv_clk_src_max) { |
157 | if (domain->flags & NVKM_CLK_DOM_FLAG_CORE) { |
158 | u32 freq = nouveau_clock_adjust(clk, true, |
159 | pstate->pstate, |
160 | domain->bios, |
161 | cstepX.freq); |
162 | cstate->domain[domain->name] = freq; |
163 | } |
164 | domain++; |
165 | } |
166 | |
167 | list_add(&cstate->head, &pstate->list); |
168 | return 0; |
169 | } |
170 | |
171 | /****************************************************************************** |
172 | * P-States |
173 | *****************************************************************************/ |
174 | static int |
175 | nouveau_pstate_prog(struct nouveau_clock *clk, int pstatei) |
176 | { |
177 | struct nouveau_fb *pfb = nouveau_fb(clk); |
178 | struct nouveau_pstate *pstate; |
179 | int ret, idx = 0; |
180 | |
181 | list_for_each_entry(pstate, &clk->states, head) { |
182 | if (idx++ == pstatei) |
183 | break; |
184 | } |
185 | |
186 | nv_debug(clk, "setting performance state %d\n" , pstatei); |
187 | clk->pstate = pstatei; |
188 | |
189 | if (pfb->ram->calc) { |
190 | int khz = pstate->base.domain[nv_clk_src_mem]; |
191 | do { |
192 | ret = pfb->ram->calc(pfb, khz); |
193 | if (ret == 0) |
194 | ret = pfb->ram->prog(pfb); |
195 | } while (ret > 0); |
196 | pfb->ram->tidy(pfb); |
197 | } |
198 | |
199 | return nouveau_cstate_prog(clk, pstate, 0); |
200 | } |
201 | |
202 | static int |
203 | nouveau_pstate_calc(struct nouveau_clock *clk) |
204 | { |
205 | int pstate, ret = 0; |
206 | |
207 | nv_trace(clk, "P %d U %d A %d T %d D %d\n" , clk->pstate, |
208 | clk->ustate, clk->astate, clk->tstate, clk->dstate); |
209 | |
210 | if (clk->state_nr && clk->ustate != -1) { |
211 | pstate = (clk->ustate < 0) ? clk->astate : clk->ustate; |
212 | pstate = min(pstate, clk->state_nr - 1 - clk->tstate); |
213 | pstate = max(pstate, clk->dstate); |
214 | } else { |
215 | pstate = clk->pstate = -1; |
216 | } |
217 | |
218 | nv_trace(clk, "-> %d\n" , pstate); |
219 | if (pstate != clk->pstate) |
220 | ret = nouveau_pstate_prog(clk, pstate); |
221 | return ret; |
222 | } |
223 | |
224 | static void |
225 | nouveau_pstate_info(struct nouveau_clock *clk, struct nouveau_pstate *pstate) |
226 | { |
227 | struct nouveau_clocks *clock = clk->domains - 1; |
228 | struct nouveau_cstate *cstate; |
229 | char info[3][32] = { "" , "" , "" }; |
230 | char name[4] = "--" ; |
231 | int i = -1; |
232 | |
233 | if (pstate->pstate != 0xff) |
234 | snprintf(name, sizeof(name), "%02x" , pstate->pstate); |
235 | |
236 | while ((++clock)->name != nv_clk_src_max) { |
237 | u32 lo = pstate->base.domain[clock->name]; |
238 | u32 hi = lo; |
239 | if (hi == 0) |
240 | continue; |
241 | |
242 | nv_debug(clk, "%02x: %10d KHz\n" , clock->name, lo); |
243 | list_for_each_entry(cstate, &pstate->list, head) { |
244 | u32 freq = cstate->domain[clock->name]; |
245 | lo = min(lo, freq); |
246 | hi = max(hi, freq); |
247 | nv_debug(clk, "%10d KHz\n" , freq); |
248 | } |
249 | |
250 | if (clock->mname && ++i < ARRAY_SIZE(info)) { |
251 | lo /= clock->mdiv; |
252 | hi /= clock->mdiv; |
253 | if (lo == hi) { |
254 | snprintf(info[i], sizeof(info[i]), "%s %d MHz" , |
255 | clock->mname, lo); |
256 | } else { |
257 | snprintf(info[i], sizeof(info[i]), |
258 | "%s %d-%d MHz" , clock->mname, lo, hi); |
259 | } |
260 | } |
261 | } |
262 | |
263 | nv_info(clk, "%s: %s %s %s\n" , name, info[0], info[1], info[2]); |
264 | } |
265 | |
266 | static void |
267 | nouveau_pstate_del(struct nouveau_pstate *pstate) |
268 | { |
269 | struct nouveau_cstate *cstate, *temp; |
270 | |
271 | list_for_each_entry_safe(cstate, temp, &pstate->list, head) { |
272 | nouveau_cstate_del(cstate); |
273 | } |
274 | |
275 | list_del(&pstate->head); |
276 | kfree(pstate); |
277 | } |
278 | |
279 | static int |
280 | nouveau_pstate_new(struct nouveau_clock *clk, int idx) |
281 | { |
282 | struct nouveau_bios *bios = nouveau_bios(clk); |
283 | struct nouveau_clocks *domain = clk->domains - 1; |
284 | struct nouveau_pstate *pstate; |
285 | struct nouveau_cstate *cstate; |
286 | struct nvbios_cstepE cstepE; |
287 | struct nvbios_perfE perfE; |
288 | u8 ver, hdr, cnt, len; |
289 | u16 data; |
290 | |
291 | data = nvbios_perfEp(bios, idx, &ver, &hdr, &cnt, &len, &perfE); |
292 | if (!data) |
293 | return -EINVAL; |
294 | if (perfE.pstate == 0xff) |
295 | return 0; |
296 | |
297 | pstate = kzalloc(sizeof(*pstate), GFP_KERNEL); |
298 | cstate = &pstate->base; |
299 | if (!pstate) |
300 | return -ENOMEM; |
301 | |
302 | INIT_LIST_HEAD(&pstate->list); |
303 | |
304 | pstate->pstate = perfE.pstate; |
305 | pstate->fanspeed = perfE.fanspeed; |
306 | cstate->voltage = perfE.voltage; |
307 | cstate->domain[nv_clk_src_core] = perfE.core; |
308 | cstate->domain[nv_clk_src_shader] = perfE.shader; |
309 | cstate->domain[nv_clk_src_mem] = perfE.memory; |
310 | cstate->domain[nv_clk_src_vdec] = perfE.vdec; |
311 | cstate->domain[nv_clk_src_dom6] = perfE.disp; |
312 | |
313 | while (ver >= 0x40 && (++domain)->name != nv_clk_src_max) { |
314 | struct nvbios_perfS perfS; |
315 | u8 sver = ver, shdr = hdr; |
316 | u32 perfSe = nvbios_perfSp(bios, data, domain->bios, |
317 | &sver, &shdr, cnt, len, &perfS); |
318 | if (perfSe == 0 || sver != 0x40) |
319 | continue; |
320 | |
321 | if (domain->flags & NVKM_CLK_DOM_FLAG_CORE) { |
322 | perfS.v40.freq = nouveau_clock_adjust(clk, false, |
323 | pstate->pstate, |
324 | domain->bios, |
325 | perfS.v40.freq); |
326 | } |
327 | |
328 | cstate->domain[domain->name] = perfS.v40.freq; |
329 | } |
330 | |
331 | data = nvbios_cstepEm(bios, pstate->pstate, &ver, &hdr, &cstepE); |
332 | if (data) { |
333 | int idx = cstepE.index; |
334 | do { |
335 | nouveau_cstate_new(clk, idx, pstate); |
336 | } while(idx--); |
337 | } |
338 | |
339 | nouveau_pstate_info(clk, pstate); |
340 | list_add_tail(&pstate->head, &clk->states); |
341 | clk->state_nr++; |
342 | return 0; |
343 | } |
344 | |
345 | /****************************************************************************** |
346 | * Adjustment triggers |
347 | *****************************************************************************/ |
348 | static int |
349 | nouveau_clock_ustate_update(struct nouveau_clock *clk, int req) |
350 | { |
351 | struct nouveau_pstate *pstate; |
352 | int i = 0; |
353 | |
354 | /* YKW repellant */ |
355 | return -ENOSYS; |
356 | |
357 | if (req != -1 && req != -2) { |
358 | list_for_each_entry(pstate, &clk->states, head) { |
359 | if (pstate->pstate == req) |
360 | break; |
361 | i++; |
362 | } |
363 | |
364 | if (pstate->pstate != req) |
365 | return -EINVAL; |
366 | req = i; |
367 | } |
368 | |
369 | clk->ustate = req; |
370 | return 0; |
371 | } |
372 | |
373 | int |
374 | nouveau_clock_ustate(struct nouveau_clock *clk, int req) |
375 | { |
376 | int ret = nouveau_clock_ustate_update(clk, req); |
377 | if (ret) |
378 | return ret; |
379 | return nouveau_pstate_calc(clk); |
380 | } |
381 | |
382 | int |
383 | nouveau_clock_astate(struct nouveau_clock *clk, int req, int rel) |
384 | { |
385 | if (!rel) clk->astate = req; |
386 | if ( rel) clk->astate += rel; |
387 | clk->astate = min(clk->astate, clk->state_nr - 1); |
388 | clk->astate = max(clk->astate, 0); |
389 | return nouveau_pstate_calc(clk); |
390 | } |
391 | |
392 | int |
393 | nouveau_clock_tstate(struct nouveau_clock *clk, int req, int rel) |
394 | { |
395 | if (!rel) clk->tstate = req; |
396 | if ( rel) clk->tstate += rel; |
397 | clk->tstate = min(clk->tstate, 0); |
398 | clk->tstate = max(clk->tstate, -(clk->state_nr - 1)); |
399 | return nouveau_pstate_calc(clk); |
400 | } |
401 | |
402 | int |
403 | nouveau_clock_dstate(struct nouveau_clock *clk, int req, int rel) |
404 | { |
405 | if (!rel) clk->dstate = req; |
406 | if ( rel) clk->dstate += rel; |
407 | clk->dstate = min(clk->dstate, clk->state_nr - 1); |
408 | clk->dstate = max(clk->dstate, 0); |
409 | return nouveau_pstate_calc(clk); |
410 | } |
411 | |
412 | /****************************************************************************** |
413 | * subdev base class implementation |
414 | *****************************************************************************/ |
415 | int |
416 | _nouveau_clock_init(struct nouveau_object *object) |
417 | { |
418 | struct nouveau_clock *clk = (void *)object; |
419 | struct nouveau_clocks *clock = clk->domains; |
420 | int ret; |
421 | |
422 | memset(&clk->bstate, 0x00, sizeof(clk->bstate)); |
423 | INIT_LIST_HEAD(&clk->bstate.list); |
424 | clk->bstate.pstate = 0xff; |
425 | |
426 | while (clock->name != nv_clk_src_max) { |
427 | ret = clk->read(clk, clock->name); |
428 | if (ret < 0) { |
429 | nv_error(clk, "%02x freq unknown\n" , clock->name); |
430 | return ret; |
431 | } |
432 | clk->bstate.base.domain[clock->name] = ret; |
433 | clock++; |
434 | } |
435 | |
436 | nouveau_pstate_info(clk, &clk->bstate); |
437 | |
438 | clk->astate = clk->state_nr - 1; |
439 | clk->tstate = 0; |
440 | clk->dstate = 0; |
441 | clk->pstate = -1; |
442 | nouveau_pstate_calc(clk); |
443 | return 0; |
444 | } |
445 | |
446 | void |
447 | _nouveau_clock_dtor(struct nouveau_object *object) |
448 | { |
449 | struct nouveau_clock *clk = (void *)object; |
450 | struct nouveau_pstate *pstate, *temp; |
451 | |
452 | list_for_each_entry_safe(pstate, temp, &clk->states, head) { |
453 | nouveau_pstate_del(pstate); |
454 | } |
455 | |
456 | nouveau_subdev_destroy(&clk->base); |
457 | } |
458 | |
459 | int |
460 | nouveau_clock_create_(struct nouveau_object *parent, |
461 | struct nouveau_object *engine, |
462 | struct nouveau_oclass *oclass, |
463 | struct nouveau_clocks *clocks, |
464 | int length, void **object) |
465 | { |
466 | struct nouveau_device *device = nv_device(parent); |
467 | struct nouveau_clock *clk; |
468 | int ret, idx, arglen; |
469 | const char *mode; |
470 | |
471 | ret = nouveau_subdev_create_(parent, engine, oclass, 0, "CLK" , |
472 | "clock" , length, object); |
473 | clk = *object; |
474 | if (ret) |
475 | return ret; |
476 | |
477 | INIT_LIST_HEAD(&clk->states); |
478 | clk->domains = clocks; |
479 | clk->ustate = -1; |
480 | |
481 | idx = 0; |
482 | do { |
483 | ret = nouveau_pstate_new(clk, idx++); |
484 | } while (ret == 0); |
485 | |
486 | mode = nouveau_stropt(device->cfgopt, "NvClkMode" , &arglen); |
487 | if (mode) { |
488 | if (!strncasecmpz(mode, "disabled" , arglen)) { |
489 | clk->ustate = -1; |
490 | } else { |
491 | char *m = kstrndup(mode, arglen, GFP_KERNEL); |
492 | long v; |
493 | |
494 | if (!kstrtol(m, 0, &v)) |
495 | nouveau_clock_ustate_update(clk, v); |
496 | kfree(m); |
497 | } |
498 | } |
499 | |
500 | return 0; |
501 | } |
502 | |