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 | */ | |
6625f55c | 24 | #define mcp77_clk(p) container_of((p), struct mcp77_clk, base) |
7632b30e BS |
25 | #include "gt215.h" |
26 | #include "pll.h" | |
a7e4201f | 27 | |
a7e4201f RS |
28 | #include <subdev/bios.h> |
29 | #include <subdev/bios/pll.h> | |
30 | #include <subdev/timer.h> | |
a7e4201f | 31 | |
3eca809b | 32 | struct mcp77_clk { |
7632b30e | 33 | struct nvkm_clk base; |
a7e4201f RS |
34 | enum nv_clk_src csrc, ssrc, vsrc; |
35 | u32 cctrl, sctrl; | |
36 | u32 ccoef, scoef; | |
37 | u32 cpost, spost; | |
38 | u32 vdiv; | |
39 | }; | |
40 | ||
41 | static u32 | |
3eca809b | 42 | read_div(struct mcp77_clk *clk) |
a7e4201f | 43 | { |
822ad79f BS |
44 | struct nvkm_device *device = clk->base.subdev.device; |
45 | return nvkm_rd32(device, 0x004600); | |
a7e4201f RS |
46 | } |
47 | ||
48 | static u32 | |
3eca809b | 49 | read_pll(struct mcp77_clk *clk, u32 base) |
a7e4201f | 50 | { |
822ad79f BS |
51 | struct nvkm_device *device = clk->base.subdev.device; |
52 | u32 ctrl = nvkm_rd32(device, base + 0); | |
53 | u32 coef = nvkm_rd32(device, base + 4); | |
6625f55c | 54 | u32 ref = nvkm_clk_read(&clk->base, nv_clk_src_href); |
a7e4201f RS |
55 | u32 post_div = 0; |
56 | u32 clock = 0; | |
57 | int N1, M1; | |
58 | ||
59 | switch (base){ | |
60 | case 0x4020: | |
822ad79f | 61 | post_div = 1 << ((nvkm_rd32(device, 0x4070) & 0x000f0000) >> 16); |
a7e4201f RS |
62 | break; |
63 | case 0x4028: | |
822ad79f | 64 | post_div = (nvkm_rd32(device, 0x4040) & 0x000f0000) >> 16; |
a7e4201f RS |
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 | |
6625f55c | 81 | mcp77_clk_read(struct nvkm_clk *base, enum nv_clk_src src) |
a7e4201f | 82 | { |
6625f55c | 83 | struct mcp77_clk *clk = mcp77_clk(base); |
b907649e BS |
84 | struct nvkm_subdev *subdev = &clk->base.subdev; |
85 | struct nvkm_device *device = subdev->device; | |
822ad79f | 86 | u32 mast = nvkm_rd32(device, 0x00c054); |
a7e4201f RS |
87 | u32 P = 0; |
88 | ||
89 | switch (src) { | |
90 | case nv_clk_src_crystal: | |
822ad79f | 91 | return device->crystal; |
a7e4201f RS |
92 | case nv_clk_src_href: |
93 | return 100000; /* PCIE reference clock */ | |
94 | case nv_clk_src_hclkm4: | |
6625f55c | 95 | return nvkm_clk_read(&clk->base, nv_clk_src_href) * 4; |
a7e4201f | 96 | case nv_clk_src_hclkm2d3: |
6625f55c | 97 | return nvkm_clk_read(&clk->base, nv_clk_src_href) * 2 / 3; |
a7e4201f RS |
98 | case nv_clk_src_host: |
99 | switch (mast & 0x000c0000) { | |
6625f55c | 100 | case 0x00000000: return nvkm_clk_read(&clk->base, nv_clk_src_hclkm2d3); |
a7e4201f | 101 | case 0x00040000: break; |
6625f55c BS |
102 | case 0x00080000: return nvkm_clk_read(&clk->base, nv_clk_src_hclkm4); |
103 | case 0x000c0000: return nvkm_clk_read(&clk->base, nv_clk_src_cclk); | |
a7e4201f RS |
104 | } |
105 | break; | |
106 | case nv_clk_src_core: | |
822ad79f | 107 | P = (nvkm_rd32(device, 0x004028) & 0x00070000) >> 16; |
a7e4201f RS |
108 | |
109 | switch (mast & 0x00000003) { | |
6625f55c | 110 | case 0x00000000: return nvkm_clk_read(&clk->base, nv_clk_src_crystal) >> P; |
a7e4201f | 111 | case 0x00000001: return 0; |
6625f55c | 112 | case 0x00000002: return nvkm_clk_read(&clk->base, nv_clk_src_hclkm4) >> P; |
a7e4201f RS |
113 | case 0x00000003: return read_pll(clk, 0x004028) >> P; |
114 | } | |
115 | break; | |
116 | case nv_clk_src_cclk: | |
117 | if ((mast & 0x03000000) != 0x03000000) | |
6625f55c | 118 | return nvkm_clk_read(&clk->base, nv_clk_src_core); |
a7e4201f RS |
119 | |
120 | if ((mast & 0x00000200) == 0x00000000) | |
6625f55c | 121 | return nvkm_clk_read(&clk->base, nv_clk_src_core); |
a7e4201f RS |
122 | |
123 | switch (mast & 0x00000c00) { | |
6625f55c BS |
124 | case 0x00000000: return nvkm_clk_read(&clk->base, nv_clk_src_href); |
125 | case 0x00000400: return nvkm_clk_read(&clk->base, nv_clk_src_hclkm4); | |
126 | case 0x00000800: return nvkm_clk_read(&clk->base, nv_clk_src_hclkm2d3); | |
a7e4201f RS |
127 | default: return 0; |
128 | } | |
129 | case nv_clk_src_shader: | |
822ad79f | 130 | P = (nvkm_rd32(device, 0x004020) & 0x00070000) >> 16; |
a7e4201f RS |
131 | switch (mast & 0x00000030) { |
132 | case 0x00000000: | |
133 | if (mast & 0x00000040) | |
6625f55c BS |
134 | return nvkm_clk_read(&clk->base, nv_clk_src_href) >> P; |
135 | return nvkm_clk_read(&clk->base, nv_clk_src_crystal) >> P; | |
a7e4201f RS |
136 | case 0x00000010: break; |
137 | case 0x00000020: return read_pll(clk, 0x004028) >> P; | |
138 | case 0x00000030: return read_pll(clk, 0x004020) >> P; | |
139 | } | |
140 | break; | |
141 | case nv_clk_src_mem: | |
142 | return 0; | |
143 | break; | |
144 | case nv_clk_src_vdec: | |
145 | P = (read_div(clk) & 0x00000700) >> 8; | |
146 | ||
147 | switch (mast & 0x00400000) { | |
148 | case 0x00400000: | |
6625f55c | 149 | return nvkm_clk_read(&clk->base, nv_clk_src_core) >> P; |
a7e4201f RS |
150 | break; |
151 | default: | |
152 | return 500000 >> P; | |
153 | break; | |
154 | } | |
155 | break; | |
156 | default: | |
157 | break; | |
158 | } | |
159 | ||
b907649e | 160 | nvkm_debug(subdev, "unknown clock source %d %08x\n", src, mast); |
a7e4201f RS |
161 | return 0; |
162 | } | |
163 | ||
164 | static u32 | |
3eca809b | 165 | calc_pll(struct mcp77_clk *clk, u32 reg, |
a7e4201f RS |
166 | u32 clock, int *N, int *M, int *P) |
167 | { | |
46484438 | 168 | struct nvkm_subdev *subdev = &clk->base.subdev; |
a7e4201f | 169 | struct nvbios_pll pll; |
a7e4201f RS |
170 | int ret; |
171 | ||
46484438 | 172 | ret = nvbios_pll_parse(subdev->device->bios, reg, &pll); |
a7e4201f RS |
173 | if (ret) |
174 | return 0; | |
175 | ||
176 | pll.vco2.max_freq = 0; | |
6625f55c | 177 | pll.refclk = nvkm_clk_read(&clk->base, nv_clk_src_href); |
a7e4201f RS |
178 | if (!pll.refclk) |
179 | return 0; | |
180 | ||
46484438 | 181 | return nv04_pll_calc(subdev, &pll, clock, N, M, NULL, NULL, P); |
a7e4201f RS |
182 | } |
183 | ||
184 | static inline u32 | |
185 | calc_P(u32 src, u32 target, int *div) | |
186 | { | |
187 | u32 clk0 = src, clk1 = src; | |
188 | for (*div = 0; *div <= 7; (*div)++) { | |
189 | if (clk0 <= target) { | |
190 | clk1 = clk0 << (*div ? 1 : 0); | |
191 | break; | |
192 | } | |
193 | clk0 >>= 1; | |
194 | } | |
195 | ||
196 | if (target - clk0 <= clk1 - target) | |
197 | return clk0; | |
198 | (*div)--; | |
199 | return clk1; | |
200 | } | |
201 | ||
202 | static int | |
6625f55c | 203 | mcp77_clk_calc(struct nvkm_clk *base, struct nvkm_cstate *cstate) |
a7e4201f | 204 | { |
6625f55c | 205 | struct mcp77_clk *clk = mcp77_clk(base); |
a7e4201f RS |
206 | const int shader = cstate->domain[nv_clk_src_shader]; |
207 | const int core = cstate->domain[nv_clk_src_core]; | |
208 | const int vdec = cstate->domain[nv_clk_src_vdec]; | |
b907649e | 209 | struct nvkm_subdev *subdev = &clk->base.subdev; |
a7e4201f RS |
210 | u32 out = 0, clock = 0; |
211 | int N, M, P1, P2 = 0; | |
212 | int divs = 0; | |
213 | ||
214 | /* cclk: find suitable source, disable PLL if we can */ | |
6625f55c BS |
215 | if (core < nvkm_clk_read(&clk->base, nv_clk_src_hclkm4)) |
216 | out = calc_P(nvkm_clk_read(&clk->base, nv_clk_src_hclkm4), core, &divs); | |
a7e4201f RS |
217 | |
218 | /* Calculate clock * 2, so shader clock can use it too */ | |
3eca809b | 219 | clock = calc_pll(clk, 0x4028, (core << 1), &N, &M, &P1); |
a7e4201f | 220 | |
7632b30e | 221 | if (abs(core - out) <= abs(core - (clock >> 1))) { |
3eca809b BS |
222 | clk->csrc = nv_clk_src_hclkm4; |
223 | clk->cctrl = divs << 16; | |
a7e4201f RS |
224 | } else { |
225 | /* NVCTRL is actually used _after_ NVPOST, and after what we | |
226 | * call NVPLL. To make matters worse, NVPOST is an integer | |
227 | * divider instead of a right-shift number. */ | |
228 | if(P1 > 2) { | |
229 | P2 = P1 - 2; | |
230 | P1 = 2; | |
231 | } | |
232 | ||
3eca809b BS |
233 | clk->csrc = nv_clk_src_core; |
234 | clk->ccoef = (N << 8) | M; | |
a7e4201f | 235 | |
3eca809b BS |
236 | clk->cctrl = (P2 + 1) << 16; |
237 | clk->cpost = (1 << P1) << 16; | |
a7e4201f RS |
238 | } |
239 | ||
240 | /* sclk: nvpll + divisor, href or spll */ | |
241 | out = 0; | |
6625f55c | 242 | if (shader == nvkm_clk_read(&clk->base, nv_clk_src_href)) { |
3eca809b | 243 | clk->ssrc = nv_clk_src_href; |
a7e4201f | 244 | } else { |
3eca809b BS |
245 | clock = calc_pll(clk, 0x4020, shader, &N, &M, &P1); |
246 | if (clk->csrc == nv_clk_src_core) | |
a7e4201f | 247 | out = calc_P((core << 1), shader, &divs); |
a7e4201f RS |
248 | |
249 | if (abs(shader - out) <= | |
250 | abs(shader - clock) && | |
251 | (divs + P2) <= 7) { | |
3eca809b BS |
252 | clk->ssrc = nv_clk_src_core; |
253 | clk->sctrl = (divs + P2) << 16; | |
a7e4201f | 254 | } else { |
3eca809b BS |
255 | clk->ssrc = nv_clk_src_shader; |
256 | clk->scoef = (N << 8) | M; | |
257 | clk->sctrl = P1 << 16; | |
a7e4201f RS |
258 | } |
259 | } | |
260 | ||
261 | /* vclk */ | |
262 | out = calc_P(core, vdec, &divs); | |
263 | clock = calc_P(500000, vdec, &P1); | |
7632b30e | 264 | if(abs(vdec - out) <= abs(vdec - clock)) { |
3eca809b BS |
265 | clk->vsrc = nv_clk_src_cclk; |
266 | clk->vdiv = divs << 16; | |
a7e4201f | 267 | } else { |
3eca809b BS |
268 | clk->vsrc = nv_clk_src_vdec; |
269 | clk->vdiv = P1 << 16; | |
a7e4201f RS |
270 | } |
271 | ||
272 | /* Print strategy! */ | |
b907649e BS |
273 | nvkm_debug(subdev, "nvpll: %08x %08x %08x\n", |
274 | clk->ccoef, clk->cpost, clk->cctrl); | |
275 | nvkm_debug(subdev, " spll: %08x %08x %08x\n", | |
276 | clk->scoef, clk->spost, clk->sctrl); | |
277 | nvkm_debug(subdev, " vdiv: %08x\n", clk->vdiv); | |
3eca809b | 278 | if (clk->csrc == nv_clk_src_hclkm4) |
b907649e | 279 | nvkm_debug(subdev, "core: hrefm4\n"); |
a7e4201f | 280 | else |
b907649e | 281 | nvkm_debug(subdev, "core: nvpll\n"); |
a7e4201f | 282 | |
3eca809b | 283 | if (clk->ssrc == nv_clk_src_hclkm4) |
b907649e | 284 | nvkm_debug(subdev, "shader: hrefm4\n"); |
3eca809b | 285 | else if (clk->ssrc == nv_clk_src_core) |
b907649e | 286 | nvkm_debug(subdev, "shader: nvpll\n"); |
a7e4201f | 287 | else |
b907649e | 288 | nvkm_debug(subdev, "shader: spll\n"); |
a7e4201f | 289 | |
3eca809b | 290 | if (clk->vsrc == nv_clk_src_hclkm4) |
b907649e | 291 | nvkm_debug(subdev, "vdec: 500MHz\n"); |
a7e4201f | 292 | else |
b907649e | 293 | nvkm_debug(subdev, "vdec: core\n"); |
a7e4201f RS |
294 | |
295 | return 0; | |
296 | } | |
297 | ||
298 | static int | |
6625f55c | 299 | mcp77_clk_prog(struct nvkm_clk *base) |
a7e4201f | 300 | { |
6625f55c | 301 | struct mcp77_clk *clk = mcp77_clk(base); |
b907649e BS |
302 | struct nvkm_subdev *subdev = &clk->base.subdev; |
303 | struct nvkm_device *device = subdev->device; | |
2fe7eaa0 | 304 | u32 pllmask = 0, mast; |
a7e4201f | 305 | unsigned long flags; |
2fe7eaa0 RS |
306 | unsigned long *f = &flags; |
307 | int ret = 0; | |
a7e4201f | 308 | |
3eca809b | 309 | ret = gt215_clk_pre(&clk->base, f); |
2fe7eaa0 RS |
310 | if (ret) |
311 | goto out; | |
a7e4201f RS |
312 | |
313 | /* First switch to safe clocks: href */ | |
822ad79f | 314 | mast = nvkm_mask(device, 0xc054, 0x03400e70, 0x03400640); |
a7e4201f RS |
315 | mast &= ~0x00400e73; |
316 | mast |= 0x03000000; | |
317 | ||
3eca809b | 318 | switch (clk->csrc) { |
a7e4201f | 319 | case nv_clk_src_hclkm4: |
822ad79f | 320 | nvkm_mask(device, 0x4028, 0x00070000, clk->cctrl); |
a7e4201f RS |
321 | mast |= 0x00000002; |
322 | break; | |
323 | case nv_clk_src_core: | |
822ad79f BS |
324 | nvkm_wr32(device, 0x402c, clk->ccoef); |
325 | nvkm_wr32(device, 0x4028, 0x80000000 | clk->cctrl); | |
326 | nvkm_wr32(device, 0x4040, clk->cpost); | |
a7e4201f RS |
327 | pllmask |= (0x3 << 8); |
328 | mast |= 0x00000003; | |
329 | break; | |
330 | default: | |
b907649e | 331 | nvkm_warn(subdev, "Reclocking failed: unknown core clock\n"); |
a7e4201f RS |
332 | goto resume; |
333 | } | |
334 | ||
3eca809b | 335 | switch (clk->ssrc) { |
a7e4201f | 336 | case nv_clk_src_href: |
822ad79f | 337 | nvkm_mask(device, 0x4020, 0x00070000, 0x00000000); |
a7e4201f RS |
338 | /* mast |= 0x00000000; */ |
339 | break; | |
340 | case nv_clk_src_core: | |
822ad79f | 341 | nvkm_mask(device, 0x4020, 0x00070000, clk->sctrl); |
a7e4201f RS |
342 | mast |= 0x00000020; |
343 | break; | |
344 | case nv_clk_src_shader: | |
822ad79f BS |
345 | nvkm_wr32(device, 0x4024, clk->scoef); |
346 | nvkm_wr32(device, 0x4020, 0x80000000 | clk->sctrl); | |
347 | nvkm_wr32(device, 0x4070, clk->spost); | |
a7e4201f RS |
348 | pllmask |= (0x3 << 12); |
349 | mast |= 0x00000030; | |
350 | break; | |
351 | default: | |
b907649e | 352 | nvkm_warn(subdev, "Reclocking failed: unknown sclk clock\n"); |
a7e4201f RS |
353 | goto resume; |
354 | } | |
355 | ||
6979c630 BS |
356 | if (nvkm_msec(device, 2000, |
357 | u32 tmp = nvkm_rd32(device, 0x004080) & pllmask; | |
358 | if (tmp == pllmask) | |
359 | break; | |
360 | ) < 0) | |
a7e4201f | 361 | goto resume; |
a7e4201f | 362 | |
3eca809b | 363 | switch (clk->vsrc) { |
a7e4201f RS |
364 | case nv_clk_src_cclk: |
365 | mast |= 0x00400000; | |
366 | default: | |
822ad79f | 367 | nvkm_wr32(device, 0x4600, clk->vdiv); |
a7e4201f RS |
368 | } |
369 | ||
822ad79f | 370 | nvkm_wr32(device, 0xc054, mast); |
a7e4201f RS |
371 | |
372 | resume: | |
a7e4201f | 373 | /* Disable some PLLs and dividers when unused */ |
3eca809b | 374 | if (clk->csrc != nv_clk_src_core) { |
822ad79f BS |
375 | nvkm_wr32(device, 0x4040, 0x00000000); |
376 | nvkm_mask(device, 0x4028, 0x80000000, 0x00000000); | |
a7e4201f RS |
377 | } |
378 | ||
3eca809b | 379 | if (clk->ssrc != nv_clk_src_shader) { |
822ad79f BS |
380 | nvkm_wr32(device, 0x4070, 0x00000000); |
381 | nvkm_mask(device, 0x4020, 0x80000000, 0x00000000); | |
a7e4201f RS |
382 | } |
383 | ||
2fe7eaa0 RS |
384 | out: |
385 | if (ret == -EBUSY) | |
386 | f = NULL; | |
387 | ||
3eca809b | 388 | gt215_clk_post(&clk->base, f); |
a7e4201f RS |
389 | return ret; |
390 | } | |
391 | ||
392 | static void | |
6625f55c | 393 | mcp77_clk_tidy(struct nvkm_clk *base) |
a7e4201f RS |
394 | { |
395 | } | |
396 | ||
6625f55c BS |
397 | static const struct nvkm_clk_func |
398 | mcp77_clk = { | |
399 | .read = mcp77_clk_read, | |
400 | .calc = mcp77_clk_calc, | |
401 | .prog = mcp77_clk_prog, | |
402 | .tidy = mcp77_clk_tidy, | |
403 | .domains = { | |
404 | { nv_clk_src_crystal, 0xff }, | |
405 | { nv_clk_src_href , 0xff }, | |
406 | { nv_clk_src_core , 0xff, 0, "core", 1000 }, | |
407 | { nv_clk_src_shader , 0xff, 0, "shader", 1000 }, | |
408 | { nv_clk_src_vdec , 0xff, 0, "vdec", 1000 }, | |
409 | { nv_clk_src_max } | |
410 | } | |
a7e4201f RS |
411 | }; |
412 | ||
6625f55c BS |
413 | int |
414 | mcp77_clk_new(struct nvkm_device *device, int index, struct nvkm_clk **pclk) | |
a7e4201f | 415 | { |
3eca809b | 416 | struct mcp77_clk *clk; |
a7e4201f | 417 | |
6625f55c BS |
418 | if (!(clk = kzalloc(sizeof(*clk), GFP_KERNEL))) |
419 | return -ENOMEM; | |
420 | *pclk = &clk->base; | |
a7e4201f | 421 | |
6625f55c | 422 | return nvkm_clk_ctor(&mcp77_clk, device, index, true, &clk->base); |
a7e4201f | 423 | } |