Commit | Line | Data |
---|---|---|
a7e4201f RS |
1 | /* |
2 | * Copyright 2012 Red Hat Inc. | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
20 | * OTHER DEALINGS IN THE SOFTWARE. | |
21 | * | |
22 | * Authors: Ben Skeggs | |
23 | */ | |
24 | ||
25 | #include <engine/fifo.h> | |
26 | #include <subdev/bios.h> | |
27 | #include <subdev/bios/pll.h> | |
28 | #include <subdev/timer.h> | |
29 | #include <subdev/clock.h> | |
30 | ||
2fe7eaa0 | 31 | #include "nva3.h" |
a7e4201f RS |
32 | #include "pll.h" |
33 | ||
34 | struct nvaa_clock_priv { | |
35 | struct nouveau_clock base; | |
36 | enum nv_clk_src csrc, ssrc, vsrc; | |
37 | u32 cctrl, sctrl; | |
38 | u32 ccoef, scoef; | |
39 | u32 cpost, spost; | |
40 | u32 vdiv; | |
41 | }; | |
42 | ||
43 | static u32 | |
44 | read_div(struct nouveau_clock *clk) | |
45 | { | |
46 | return nv_rd32(clk, 0x004600); | |
47 | } | |
48 | ||
49 | static u32 | |
50 | read_pll(struct nouveau_clock *clk, u32 base) | |
51 | { | |
52 | u32 ctrl = nv_rd32(clk, base + 0); | |
53 | u32 coef = nv_rd32(clk, base + 4); | |
54 | u32 ref = clk->read(clk, nv_clk_src_href); | |
55 | u32 post_div = 0; | |
56 | u32 clock = 0; | |
57 | int N1, M1; | |
58 | ||
59 | switch (base){ | |
60 | case 0x4020: | |
61 | post_div = 1 << ((nv_rd32(clk, 0x4070) & 0x000f0000) >> 16); | |
62 | break; | |
63 | case 0x4028: | |
64 | post_div = (nv_rd32(clk, 0x4040) & 0x000f0000) >> 16; | |
65 | break; | |
66 | default: | |
67 | break; | |
68 | } | |
69 | ||
70 | N1 = (coef & 0x0000ff00) >> 8; | |
71 | M1 = (coef & 0x000000ff); | |
72 | if ((ctrl & 0x80000000) && M1) { | |
73 | clock = ref * N1 / M1; | |
74 | clock = clock / post_div; | |
75 | } | |
76 | ||
77 | return clock; | |
78 | } | |
79 | ||
80 | static int | |
81 | nvaa_clock_read(struct nouveau_clock *clk, enum nv_clk_src src) | |
82 | { | |
83 | struct nvaa_clock_priv *priv = (void *)clk; | |
84 | u32 mast = nv_rd32(clk, 0x00c054); | |
85 | u32 P = 0; | |
86 | ||
87 | switch (src) { | |
88 | case nv_clk_src_crystal: | |
89 | return nv_device(priv)->crystal; | |
90 | case nv_clk_src_href: | |
91 | return 100000; /* PCIE reference clock */ | |
92 | case nv_clk_src_hclkm4: | |
93 | return clk->read(clk, nv_clk_src_href) * 4; | |
94 | case nv_clk_src_hclkm2d3: | |
95 | return clk->read(clk, nv_clk_src_href) * 2 / 3; | |
96 | case nv_clk_src_host: | |
97 | switch (mast & 0x000c0000) { | |
98 | case 0x00000000: return clk->read(clk, nv_clk_src_hclkm2d3); | |
99 | case 0x00040000: break; | |
100 | case 0x00080000: return clk->read(clk, nv_clk_src_hclkm4); | |
101 | case 0x000c0000: return clk->read(clk, nv_clk_src_cclk); | |
102 | } | |
103 | break; | |
104 | case nv_clk_src_core: | |
105 | P = (nv_rd32(clk, 0x004028) & 0x00070000) >> 16; | |
106 | ||
107 | switch (mast & 0x00000003) { | |
108 | case 0x00000000: return clk->read(clk, nv_clk_src_crystal) >> P; | |
109 | case 0x00000001: return 0; | |
110 | case 0x00000002: return clk->read(clk, nv_clk_src_hclkm4) >> P; | |
111 | case 0x00000003: return read_pll(clk, 0x004028) >> P; | |
112 | } | |
113 | break; | |
114 | case nv_clk_src_cclk: | |
115 | if ((mast & 0x03000000) != 0x03000000) | |
116 | return clk->read(clk, nv_clk_src_core); | |
117 | ||
118 | if ((mast & 0x00000200) == 0x00000000) | |
119 | return clk->read(clk, nv_clk_src_core); | |
120 | ||
121 | switch (mast & 0x00000c00) { | |
122 | case 0x00000000: return clk->read(clk, nv_clk_src_href); | |
123 | case 0x00000400: return clk->read(clk, nv_clk_src_hclkm4); | |
124 | case 0x00000800: return clk->read(clk, nv_clk_src_hclkm2d3); | |
125 | default: return 0; | |
126 | } | |
127 | case nv_clk_src_shader: | |
128 | P = (nv_rd32(clk, 0x004020) & 0x00070000) >> 16; | |
129 | switch (mast & 0x00000030) { | |
130 | case 0x00000000: | |
131 | if (mast & 0x00000040) | |
132 | return clk->read(clk, nv_clk_src_href) >> P; | |
133 | return clk->read(clk, nv_clk_src_crystal) >> P; | |
134 | case 0x00000010: break; | |
135 | case 0x00000020: return read_pll(clk, 0x004028) >> P; | |
136 | case 0x00000030: return read_pll(clk, 0x004020) >> P; | |
137 | } | |
138 | break; | |
139 | case nv_clk_src_mem: | |
140 | return 0; | |
141 | break; | |
142 | case nv_clk_src_vdec: | |
143 | P = (read_div(clk) & 0x00000700) >> 8; | |
144 | ||
145 | switch (mast & 0x00400000) { | |
146 | case 0x00400000: | |
147 | return clk->read(clk, nv_clk_src_core) >> P; | |
148 | break; | |
149 | default: | |
150 | return 500000 >> P; | |
151 | break; | |
152 | } | |
153 | break; | |
154 | default: | |
155 | break; | |
156 | } | |
157 | ||
158 | nv_debug(priv, "unknown clock source %d 0x%08x\n", src, mast); | |
159 | return 0; | |
160 | } | |
161 | ||
162 | static u32 | |
163 | calc_pll(struct nvaa_clock_priv *priv, u32 reg, | |
164 | u32 clock, int *N, int *M, int *P) | |
165 | { | |
166 | struct nouveau_bios *bios = nouveau_bios(priv); | |
167 | struct nvbios_pll pll; | |
168 | struct nouveau_clock *clk = &priv->base; | |
169 | int ret; | |
170 | ||
171 | ret = nvbios_pll_parse(bios, reg, &pll); | |
172 | if (ret) | |
173 | return 0; | |
174 | ||
175 | pll.vco2.max_freq = 0; | |
176 | pll.refclk = clk->read(clk, nv_clk_src_href); | |
177 | if (!pll.refclk) | |
178 | return 0; | |
179 | ||
180 | return nv04_pll_calc(nv_subdev(priv), &pll, clock, N, M, NULL, NULL, P); | |
181 | } | |
182 | ||
183 | static inline u32 | |
184 | calc_P(u32 src, u32 target, int *div) | |
185 | { | |
186 | u32 clk0 = src, clk1 = src; | |
187 | for (*div = 0; *div <= 7; (*div)++) { | |
188 | if (clk0 <= target) { | |
189 | clk1 = clk0 << (*div ? 1 : 0); | |
190 | break; | |
191 | } | |
192 | clk0 >>= 1; | |
193 | } | |
194 | ||
195 | if (target - clk0 <= clk1 - target) | |
196 | return clk0; | |
197 | (*div)--; | |
198 | return clk1; | |
199 | } | |
200 | ||
201 | static int | |
202 | nvaa_clock_calc(struct nouveau_clock *clk, struct nouveau_cstate *cstate) | |
203 | { | |
204 | struct nvaa_clock_priv *priv = (void *)clk; | |
205 | const int shader = cstate->domain[nv_clk_src_shader]; | |
206 | const int core = cstate->domain[nv_clk_src_core]; | |
207 | const int vdec = cstate->domain[nv_clk_src_vdec]; | |
208 | u32 out = 0, clock = 0; | |
209 | int N, M, P1, P2 = 0; | |
210 | int divs = 0; | |
211 | ||
212 | /* cclk: find suitable source, disable PLL if we can */ | |
213 | if (core < clk->read(clk, nv_clk_src_hclkm4)) | |
214 | out = calc_P(clk->read(clk, nv_clk_src_hclkm4), core, &divs); | |
215 | ||
216 | /* Calculate clock * 2, so shader clock can use it too */ | |
217 | clock = calc_pll(priv, 0x4028, (core << 1), &N, &M, &P1); | |
218 | ||
219 | if (abs(core - out) <= | |
220 | abs(core - (clock >> 1))) { | |
221 | priv->csrc = nv_clk_src_hclkm4; | |
222 | priv->cctrl = divs << 16; | |
223 | } else { | |
224 | /* NVCTRL is actually used _after_ NVPOST, and after what we | |
225 | * call NVPLL. To make matters worse, NVPOST is an integer | |
226 | * divider instead of a right-shift number. */ | |
227 | if(P1 > 2) { | |
228 | P2 = P1 - 2; | |
229 | P1 = 2; | |
230 | } | |
231 | ||
232 | priv->csrc = nv_clk_src_core; | |
233 | priv->ccoef = (N << 8) | M; | |
234 | ||
235 | priv->cctrl = (P2 + 1) << 16; | |
236 | priv->cpost = (1 << P1) << 16; | |
237 | } | |
238 | ||
239 | /* sclk: nvpll + divisor, href or spll */ | |
240 | out = 0; | |
241 | if (shader == clk->read(clk, nv_clk_src_href)) { | |
242 | priv->ssrc = nv_clk_src_href; | |
243 | } else { | |
244 | clock = calc_pll(priv, 0x4020, shader, &N, &M, &P1); | |
245 | if (priv->csrc == nv_clk_src_core) { | |
246 | out = calc_P((core << 1), shader, &divs); | |
247 | } | |
248 | ||
249 | if (abs(shader - out) <= | |
250 | abs(shader - clock) && | |
251 | (divs + P2) <= 7) { | |
252 | priv->ssrc = nv_clk_src_core; | |
253 | priv->sctrl = (divs + P2) << 16; | |
254 | } else { | |
255 | priv->ssrc = nv_clk_src_shader; | |
256 | priv->scoef = (N << 8) | M; | |
257 | priv->sctrl = P1 << 16; | |
258 | } | |
259 | } | |
260 | ||
261 | /* vclk */ | |
262 | out = calc_P(core, vdec, &divs); | |
263 | clock = calc_P(500000, vdec, &P1); | |
264 | if(abs(vdec - out) <= | |
265 | abs(vdec - clock)) { | |
266 | priv->vsrc = nv_clk_src_cclk; | |
267 | priv->vdiv = divs << 16; | |
268 | } else { | |
269 | priv->vsrc = nv_clk_src_vdec; | |
270 | priv->vdiv = P1 << 16; | |
271 | } | |
272 | ||
273 | /* Print strategy! */ | |
274 | nv_debug(priv, "nvpll: %08x %08x %08x\n", | |
275 | priv->ccoef, priv->cpost, priv->cctrl); | |
276 | nv_debug(priv, " spll: %08x %08x %08x\n", | |
277 | priv->scoef, priv->spost, priv->sctrl); | |
278 | nv_debug(priv, " vdiv: %08x\n", priv->vdiv); | |
279 | if (priv->csrc == nv_clk_src_hclkm4) | |
280 | nv_debug(priv, "core: hrefm4\n"); | |
281 | else | |
282 | nv_debug(priv, "core: nvpll\n"); | |
283 | ||
284 | if (priv->ssrc == nv_clk_src_hclkm4) | |
285 | nv_debug(priv, "shader: hrefm4\n"); | |
286 | else if (priv->ssrc == nv_clk_src_core) | |
287 | nv_debug(priv, "shader: nvpll\n"); | |
288 | else | |
289 | nv_debug(priv, "shader: spll\n"); | |
290 | ||
291 | if (priv->vsrc == nv_clk_src_hclkm4) | |
292 | nv_debug(priv, "vdec: 500MHz\n"); | |
293 | else | |
294 | nv_debug(priv, "vdec: core\n"); | |
295 | ||
296 | return 0; | |
297 | } | |
298 | ||
299 | static int | |
300 | nvaa_clock_prog(struct nouveau_clock *clk) | |
301 | { | |
302 | struct nvaa_clock_priv *priv = (void *)clk; | |
2fe7eaa0 | 303 | u32 pllmask = 0, mast; |
a7e4201f | 304 | unsigned long flags; |
2fe7eaa0 RS |
305 | unsigned long *f = &flags; |
306 | int ret = 0; | |
a7e4201f | 307 | |
2fe7eaa0 RS |
308 | ret = nva3_clock_pre(clk, f); |
309 | if (ret) | |
310 | goto out; | |
a7e4201f RS |
311 | |
312 | /* First switch to safe clocks: href */ | |
313 | mast = nv_mask(clk, 0xc054, 0x03400e70, 0x03400640); | |
314 | mast &= ~0x00400e73; | |
315 | mast |= 0x03000000; | |
316 | ||
317 | switch (priv->csrc) { | |
318 | case nv_clk_src_hclkm4: | |
319 | nv_mask(clk, 0x4028, 0x00070000, priv->cctrl); | |
320 | mast |= 0x00000002; | |
321 | break; | |
322 | case nv_clk_src_core: | |
323 | nv_wr32(clk, 0x402c, priv->ccoef); | |
324 | nv_wr32(clk, 0x4028, 0x80000000 | priv->cctrl); | |
325 | nv_wr32(clk, 0x4040, priv->cpost); | |
326 | pllmask |= (0x3 << 8); | |
327 | mast |= 0x00000003; | |
328 | break; | |
329 | default: | |
330 | nv_warn(priv,"Reclocking failed: unknown core clock\n"); | |
331 | goto resume; | |
332 | } | |
333 | ||
334 | switch (priv->ssrc) { | |
335 | case nv_clk_src_href: | |
336 | nv_mask(clk, 0x4020, 0x00070000, 0x00000000); | |
337 | /* mast |= 0x00000000; */ | |
338 | break; | |
339 | case nv_clk_src_core: | |
340 | nv_mask(clk, 0x4020, 0x00070000, priv->sctrl); | |
341 | mast |= 0x00000020; | |
342 | break; | |
343 | case nv_clk_src_shader: | |
344 | nv_wr32(clk, 0x4024, priv->scoef); | |
345 | nv_wr32(clk, 0x4020, 0x80000000 | priv->sctrl); | |
346 | nv_wr32(clk, 0x4070, priv->spost); | |
347 | pllmask |= (0x3 << 12); | |
348 | mast |= 0x00000030; | |
349 | break; | |
350 | default: | |
351 | nv_warn(priv,"Reclocking failed: unknown sclk clock\n"); | |
352 | goto resume; | |
353 | } | |
354 | ||
355 | if (!nv_wait(clk, 0x004080, pllmask, pllmask)) { | |
356 | nv_warn(priv,"Reclocking failed: unstable PLLs\n"); | |
357 | goto resume; | |
358 | } | |
359 | ||
360 | switch (priv->vsrc) { | |
361 | case nv_clk_src_cclk: | |
362 | mast |= 0x00400000; | |
363 | default: | |
364 | nv_wr32(clk, 0x4600, priv->vdiv); | |
365 | } | |
366 | ||
367 | nv_wr32(clk, 0xc054, mast); | |
a7e4201f RS |
368 | |
369 | resume: | |
a7e4201f RS |
370 | /* Disable some PLLs and dividers when unused */ |
371 | if (priv->csrc != nv_clk_src_core) { | |
372 | nv_wr32(clk, 0x4040, 0x00000000); | |
373 | nv_mask(clk, 0x4028, 0x80000000, 0x00000000); | |
374 | } | |
375 | ||
376 | if (priv->ssrc != nv_clk_src_shader) { | |
377 | nv_wr32(clk, 0x4070, 0x00000000); | |
378 | nv_mask(clk, 0x4020, 0x80000000, 0x00000000); | |
379 | } | |
380 | ||
2fe7eaa0 RS |
381 | out: |
382 | if (ret == -EBUSY) | |
383 | f = NULL; | |
384 | ||
385 | nva3_clock_post(clk, f); | |
386 | ||
a7e4201f RS |
387 | return ret; |
388 | } | |
389 | ||
390 | static void | |
391 | nvaa_clock_tidy(struct nouveau_clock *clk) | |
392 | { | |
393 | } | |
394 | ||
395 | static struct nouveau_clocks | |
396 | nvaa_domains[] = { | |
397 | { nv_clk_src_crystal, 0xff }, | |
398 | { nv_clk_src_href , 0xff }, | |
399 | { nv_clk_src_core , 0xff, 0, "core", 1000 }, | |
400 | { nv_clk_src_shader , 0xff, 0, "shader", 1000 }, | |
401 | { nv_clk_src_vdec , 0xff, 0, "vdec", 1000 }, | |
402 | { nv_clk_src_max } | |
403 | }; | |
404 | ||
405 | static int | |
406 | nvaa_clock_ctor(struct nouveau_object *parent, struct nouveau_object *engine, | |
407 | struct nouveau_oclass *oclass, void *data, u32 size, | |
408 | struct nouveau_object **pobject) | |
409 | { | |
410 | struct nvaa_clock_priv *priv; | |
411 | int ret; | |
412 | ||
bb4d29df AC |
413 | ret = nouveau_clock_create(parent, engine, oclass, nvaa_domains, NULL, |
414 | 0, true, &priv); | |
a7e4201f RS |
415 | *pobject = nv_object(priv); |
416 | if (ret) | |
417 | return ret; | |
418 | ||
419 | priv->base.read = nvaa_clock_read; | |
420 | priv->base.calc = nvaa_clock_calc; | |
421 | priv->base.prog = nvaa_clock_prog; | |
422 | priv->base.tidy = nvaa_clock_tidy; | |
423 | return 0; | |
424 | } | |
425 | ||
426 | struct nouveau_oclass * | |
427 | nvaa_clock_oclass = &(struct nouveau_oclass) { | |
428 | .handle = NV_SUBDEV(CLOCK, 0xaa), | |
429 | .ofuncs = &(struct nouveau_ofuncs) { | |
430 | .ctor = nvaa_clock_ctor, | |
431 | .dtor = _nouveau_clock_dtor, | |
432 | .init = _nouveau_clock_init, | |
433 | .fini = _nouveau_clock_fini, | |
434 | }, | |
435 | }; |