Commit | Line | Data |
---|---|---|
ebb945a9 BS |
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 | */ | |
878da15a | 24 | #include "nv50.h" |
a1c93078 | 25 | #include "head.h" |
78f1ad6f | 26 | #include "ior.h" |
9b096283 | 27 | #include "channv50.h" |
2a7909c0 | 28 | #include "rootnv50.h" |
ebb945a9 | 29 | |
bf0eb898 | 30 | #include <core/client.h> |
117e1633 | 31 | #include <core/enum.h> |
bb3b0a42 | 32 | #include <core/ramht.h> |
186ecad2 | 33 | #include <subdev/bios.h> |
186ecad2 BS |
34 | #include <subdev/bios/disp.h> |
35 | #include <subdev/bios/init.h> | |
36 | #include <subdev/bios/pll.h> | |
88524bc0 | 37 | #include <subdev/devinit.h> |
2a4bd8ac | 38 | #include <subdev/timer.h> |
70cabe4a | 39 | |
70aa8670 BS |
40 | static const struct nvkm_disp_oclass * |
41 | nv50_disp_root_(struct nvkm_disp *base) | |
42 | { | |
43 | return nv50_disp(base)->func->root; | |
44 | } | |
45 | ||
70aa8670 BS |
46 | static void |
47 | nv50_disp_intr_(struct nvkm_disp *base) | |
48 | { | |
49 | struct nv50_disp *disp = nv50_disp(base); | |
50 | disp->func->intr(disp); | |
51 | } | |
52 | ||
bb3b0a42 BS |
53 | static void |
54 | nv50_disp_fini_(struct nvkm_disp *base) | |
55 | { | |
56 | struct nv50_disp *disp = nv50_disp(base); | |
57 | disp->func->fini(disp); | |
58 | } | |
59 | ||
60 | static int | |
61 | nv50_disp_init_(struct nvkm_disp *base) | |
62 | { | |
63 | struct nv50_disp *disp = nv50_disp(base); | |
64 | return disp->func->init(disp); | |
65 | } | |
66 | ||
70aa8670 BS |
67 | static void * |
68 | nv50_disp_dtor_(struct nvkm_disp *base) | |
69 | { | |
70 | struct nv50_disp *disp = nv50_disp(base); | |
bb3b0a42 BS |
71 | |
72 | nvkm_ramht_del(&disp->ramht); | |
73 | nvkm_gpuobj_del(&disp->inst); | |
74 | ||
70aa8670 | 75 | nvkm_event_fini(&disp->uevent); |
3607bfd3 BS |
76 | if (disp->wq) |
77 | destroy_workqueue(disp->wq); | |
bb3b0a42 | 78 | |
70aa8670 | 79 | return disp; |
79ca2770 BS |
80 | } |
81 | ||
3b9ba66a BS |
82 | static int |
83 | nv50_disp_oneinit_(struct nvkm_disp *base) | |
84 | { | |
85 | struct nv50_disp *disp = nv50_disp(base); | |
86 | const struct nv50_disp_func *func = disp->func; | |
f7b2ece3 | 87 | struct nvkm_subdev *subdev = &disp->base.engine.subdev; |
bb3b0a42 | 88 | struct nvkm_device *device = subdev->device; |
3b9ba66a BS |
89 | int ret, i; |
90 | ||
290ffeaf BS |
91 | if (func->wndw.cnt) { |
92 | disp->wndw.nr = func->wndw.cnt(&disp->base, &disp->wndw.mask); | |
93 | nvkm_debug(subdev, "Window(s): %d (%08lx)\n", | |
94 | disp->wndw.nr, disp->wndw.mask); | |
95 | } | |
96 | ||
f7b2ece3 BS |
97 | disp->head.nr = func->head.cnt(&disp->base, &disp->head.mask); |
98 | nvkm_debug(subdev, " Head(s): %d (%02lx)\n", | |
99 | disp->head.nr, disp->head.mask); | |
100 | for_each_set_bit(i, &disp->head.mask, disp->head.nr) { | |
3b9ba66a BS |
101 | ret = func->head.new(&disp->base, i); |
102 | if (ret) | |
103 | return ret; | |
104 | } | |
105 | ||
bf5d1a6b BS |
106 | if (func->dac.cnt) { |
107 | disp->dac.nr = func->dac.cnt(&disp->base, &disp->dac.mask); | |
108 | nvkm_debug(subdev, " DAC(s): %d (%02lx)\n", | |
109 | disp->dac.nr, disp->dac.mask); | |
110 | for_each_set_bit(i, &disp->dac.mask, disp->dac.nr) { | |
111 | ret = func->dac.new(&disp->base, i); | |
112 | if (ret) | |
113 | return ret; | |
114 | } | |
3b9ba66a BS |
115 | } |
116 | ||
f5e088d6 BS |
117 | if (func->pior.cnt) { |
118 | disp->pior.nr = func->pior.cnt(&disp->base, &disp->pior.mask); | |
119 | nvkm_debug(subdev, " PIOR(s): %d (%02lx)\n", | |
120 | disp->pior.nr, disp->pior.mask); | |
121 | for_each_set_bit(i, &disp->pior.mask, disp->pior.nr) { | |
122 | ret = func->pior.new(&disp->base, i); | |
123 | if (ret) | |
124 | return ret; | |
125 | } | |
3b9ba66a BS |
126 | } |
127 | ||
9fe4e177 BS |
128 | disp->sor.nr = func->sor.cnt(&disp->base, &disp->sor.mask); |
129 | nvkm_debug(subdev, " SOR(s): %d (%02lx)\n", | |
130 | disp->sor.nr, disp->sor.mask); | |
131 | for_each_set_bit(i, &disp->sor.mask, disp->sor.nr) { | |
3b9ba66a BS |
132 | ret = func->sor.new(&disp->base, i); |
133 | if (ret) | |
134 | return ret; | |
135 | } | |
136 | ||
bb3b0a42 BS |
137 | ret = nvkm_gpuobj_new(device, 0x10000, 0x10000, false, NULL, |
138 | &disp->inst); | |
139 | if (ret) | |
140 | return ret; | |
141 | ||
290ffeaf BS |
142 | return nvkm_ramht_new(device, func->ramht_size ? func->ramht_size : |
143 | 0x1000, 0, disp->inst, &disp->ramht); | |
3b9ba66a BS |
144 | } |
145 | ||
70aa8670 BS |
146 | static const struct nvkm_disp_func |
147 | nv50_disp_ = { | |
148 | .dtor = nv50_disp_dtor_, | |
3b9ba66a | 149 | .oneinit = nv50_disp_oneinit_, |
bb3b0a42 BS |
150 | .init = nv50_disp_init_, |
151 | .fini = nv50_disp_fini_, | |
70aa8670 BS |
152 | .intr = nv50_disp_intr_, |
153 | .root = nv50_disp_root_, | |
79ca2770 BS |
154 | }; |
155 | ||
70aa8670 BS |
156 | int |
157 | nv50_disp_new_(const struct nv50_disp_func *func, struct nvkm_device *device, | |
f7b2ece3 | 158 | int index, struct nvkm_disp **pdisp) |
70aa8670 BS |
159 | { |
160 | struct nv50_disp *disp; | |
3b9ba66a | 161 | int ret; |
70aa8670 BS |
162 | |
163 | if (!(disp = kzalloc(sizeof(*disp), GFP_KERNEL))) | |
164 | return -ENOMEM; | |
70aa8670 BS |
165 | disp->func = func; |
166 | *pdisp = &disp->base; | |
167 | ||
a1c93078 | 168 | ret = nvkm_disp_ctor(&nv50_disp_, device, index, &disp->base); |
70aa8670 BS |
169 | if (ret) |
170 | return ret; | |
171 | ||
3607bfd3 BS |
172 | disp->wq = create_singlethread_workqueue("nvkm-disp"); |
173 | if (!disp->wq) | |
174 | return -ENOMEM; | |
a1c93078 | 175 | |
3b9ba66a | 176 | INIT_WORK(&disp->supervisor, func->super); |
78f1ad6f | 177 | |
3b9ba66a BS |
178 | return nvkm_event_init(func->uevent, 1, ARRAY_SIZE(disp->chan), |
179 | &disp->uevent); | |
70aa8670 BS |
180 | } |
181 | ||
327c5581 BS |
182 | static u32 |
183 | nv50_disp_super_iedt(struct nvkm_head *head, struct nvkm_outp *outp, | |
184 | u8 *ver, u8 *hdr, u8 *cnt, u8 *len, | |
185 | struct nvbios_outp *iedt) | |
186 | { | |
187 | struct nvkm_bios *bios = head->disp->engine.subdev.device->bios; | |
188 | const u8 l = ffs(outp->info.link); | |
189 | const u16 t = outp->info.hasht; | |
190 | const u16 m = (0x0100 << head->id) | (l << 6) | outp->info.or; | |
191 | u32 data = nvbios_outp_match(bios, t, m, ver, hdr, cnt, len, iedt); | |
192 | if (!data) | |
193 | OUTP_DBG(outp, "missing IEDT for %04x:%04x", t, m); | |
194 | return data; | |
195 | } | |
196 | ||
8d7ef84d BS |
197 | static void |
198 | nv50_disp_super_ied_on(struct nvkm_head *head, | |
199 | struct nvkm_ior *ior, int id, u32 khz) | |
200 | { | |
201 | struct nvkm_subdev *subdev = &head->disp->engine.subdev; | |
202 | struct nvkm_bios *bios = subdev->device->bios; | |
203 | struct nvkm_outp *outp = ior->asy.outp; | |
204 | struct nvbios_ocfg iedtrs; | |
205 | struct nvbios_outp iedt; | |
206 | u8 ver, hdr, cnt, len, flags = 0x00; | |
207 | u32 data; | |
208 | ||
209 | if (!outp) { | |
210 | IOR_DBG(ior, "nothing to attach"); | |
211 | return; | |
212 | } | |
213 | ||
214 | /* Lookup IED table for the device. */ | |
215 | data = nv50_disp_super_iedt(head, outp, &ver, &hdr, &cnt, &len, &iedt); | |
216 | if (!data) | |
217 | return; | |
218 | ||
219 | /* Lookup IEDT runtime settings for the current configuration. */ | |
220 | if (ior->type == SOR) { | |
221 | if (ior->asy.proto == LVDS) { | |
222 | if (head->asy.or.depth == 24) | |
223 | flags |= 0x02; | |
224 | } | |
225 | if (ior->asy.link == 3) | |
226 | flags |= 0x01; | |
227 | } | |
228 | ||
229 | data = nvbios_ocfg_match(bios, data, ior->asy.proto_evo, flags, | |
230 | &ver, &hdr, &cnt, &len, &iedtrs); | |
231 | if (!data) { | |
232 | OUTP_DBG(outp, "missing IEDT RS for %02x:%02x", | |
233 | ior->asy.proto_evo, flags); | |
234 | return; | |
235 | } | |
236 | ||
237 | /* Execute the OnInt[23] script for the current frequency. */ | |
238 | data = nvbios_oclk_match(bios, iedtrs.clkcmp[id], khz); | |
239 | if (!data) { | |
240 | OUTP_DBG(outp, "missing IEDT RSS %d for %02x:%02x %d khz", | |
241 | id, ior->asy.proto_evo, flags, khz); | |
242 | return; | |
243 | } | |
244 | ||
245 | nvbios_init(subdev, data, | |
246 | init.outp = &outp->info; | |
247 | init.or = ior->id; | |
248 | init.link = ior->asy.link; | |
249 | init.head = head->id; | |
250 | ); | |
251 | } | |
252 | ||
327c5581 BS |
253 | static void |
254 | nv50_disp_super_ied_off(struct nvkm_head *head, struct nvkm_ior *ior, int id) | |
255 | { | |
256 | struct nvkm_outp *outp = ior->arm.outp; | |
257 | struct nvbios_outp iedt; | |
258 | u8 ver, hdr, cnt, len; | |
259 | u32 data; | |
260 | ||
261 | if (!outp) { | |
262 | IOR_DBG(ior, "nothing attached"); | |
263 | return; | |
264 | } | |
265 | ||
266 | data = nv50_disp_super_iedt(head, outp, &ver, &hdr, &cnt, &len, &iedt); | |
267 | if (!data) | |
268 | return; | |
269 | ||
270 | nvbios_init(&head->disp->engine.subdev, iedt.script[id], | |
271 | init.outp = &outp->info; | |
272 | init.or = ior->id; | |
273 | init.link = ior->arm.link; | |
274 | init.head = head->id; | |
275 | ); | |
276 | } | |
277 | ||
8d7ef84d BS |
278 | static struct nvkm_ior * |
279 | nv50_disp_super_ior_asy(struct nvkm_head *head) | |
280 | { | |
281 | struct nvkm_ior *ior; | |
282 | list_for_each_entry(ior, &head->disp->ior, head) { | |
283 | if (ior->asy.head & (1 << head->id)) { | |
284 | HEAD_DBG(head, "to %s", ior->name); | |
285 | return ior; | |
286 | } | |
287 | } | |
288 | HEAD_DBG(head, "nothing to attach"); | |
289 | return NULL; | |
290 | } | |
291 | ||
327c5581 BS |
292 | static struct nvkm_ior * |
293 | nv50_disp_super_ior_arm(struct nvkm_head *head) | |
294 | { | |
295 | struct nvkm_ior *ior; | |
296 | list_for_each_entry(ior, &head->disp->ior, head) { | |
297 | if (ior->arm.head & (1 << head->id)) { | |
298 | HEAD_DBG(head, "on %s", ior->name); | |
299 | return ior; | |
300 | } | |
301 | } | |
302 | HEAD_DBG(head, "nothing attached"); | |
303 | return NULL; | |
304 | } | |
305 | ||
0d93cd92 BS |
306 | void |
307 | nv50_disp_super_3_0(struct nv50_disp *disp, struct nvkm_head *head) | |
2a4bd8ac | 308 | { |
0d93cd92 | 309 | struct nvkm_ior *ior; |
2a4bd8ac | 310 | |
0d93cd92 BS |
311 | /* Determine which OR, if any, we're attaching to the head. */ |
312 | HEAD_DBG(head, "supervisor 3.0"); | |
313 | ior = nv50_disp_super_ior_asy(head); | |
314 | if (!ior) | |
2a4bd8ac BS |
315 | return; |
316 | ||
0d93cd92 BS |
317 | /* Execute OnInt3 IED script. */ |
318 | nv50_disp_super_ied_on(head, ior, 1, head->asy.hz / 1000); | |
319 | ||
320 | /* OR-specific handling. */ | |
321 | if (ior->func->war_3) | |
322 | ior->func->war_3(ior); | |
16d4c031 BS |
323 | } |
324 | ||
325 | static void | |
8d7ef84d | 326 | nv50_disp_super_2_2_dp(struct nvkm_head *head, struct nvkm_ior *ior) |
186ecad2 | 327 | { |
8d7ef84d BS |
328 | struct nvkm_subdev *subdev = &head->disp->engine.subdev; |
329 | const u32 khz = head->asy.hz / 1000; | |
330 | const u32 linkKBps = ior->dp.bw * 27000; | |
331 | const u32 symbol = 100000; | |
186ecad2 BS |
332 | int bestTU = 0, bestVTUi = 0, bestVTUf = 0, bestVTUa = 0; |
333 | int TU, VTUi, VTUf, VTUa; | |
334 | u64 link_data_rate, link_ratio, unk; | |
335 | u32 best_diff = 64 * symbol; | |
8d7ef84d | 336 | u64 h, v; |
9506140f BS |
337 | |
338 | /* symbols/hblank - algorithm taken from comments in tegra driver */ | |
8d7ef84d BS |
339 | h = head->asy.hblanke + head->asy.htotal - head->asy.hblanks - 7; |
340 | h = h * linkKBps; | |
341 | do_div(h, khz); | |
342 | h = h - (3 * ior->dp.ef) - (12 / ior->dp.nr); | |
9506140f BS |
343 | |
344 | /* symbols/vblank - algorithm taken from comments in tegra driver */ | |
8d7ef84d BS |
345 | v = head->asy.vblanks - head->asy.vblanke - 25; |
346 | v = v * linkKBps; | |
347 | do_div(v, khz); | |
348 | v = v - ((36 / ior->dp.nr) + 3) - 1; | |
9506140f | 349 | |
8d7ef84d | 350 | ior->func->dp.audio_sym(ior, head->id, h, v); |
bf2c886a | 351 | |
8d7ef84d BS |
352 | /* watermark / activesym */ |
353 | link_data_rate = (khz * head->asy.or.depth / 8) / ior->dp.nr; | |
186ecad2 BS |
354 | |
355 | /* calculate ratio of packed data rate to link symbol rate */ | |
356 | link_ratio = link_data_rate * symbol; | |
8d7ef84d | 357 | do_div(link_ratio, linkKBps); |
186ecad2 | 358 | |
8d7ef84d | 359 | for (TU = 64; ior->func->dp.activesym && TU >= 32; TU--) { |
186ecad2 BS |
360 | /* calculate average number of valid symbols in each TU */ |
361 | u32 tu_valid = link_ratio * TU; | |
362 | u32 calc, diff; | |
363 | ||
364 | /* find a hw representation for the fraction.. */ | |
365 | VTUi = tu_valid / symbol; | |
366 | calc = VTUi * symbol; | |
367 | diff = tu_valid - calc; | |
368 | if (diff) { | |
369 | if (diff >= (symbol / 2)) { | |
370 | VTUf = symbol / (symbol - diff); | |
371 | if (symbol - (VTUf * diff)) | |
372 | VTUf++; | |
373 | ||
374 | if (VTUf <= 15) { | |
375 | VTUa = 1; | |
376 | calc += symbol - (symbol / VTUf); | |
377 | } else { | |
378 | VTUa = 0; | |
379 | VTUf = 1; | |
380 | calc += symbol; | |
381 | } | |
382 | } else { | |
383 | VTUa = 0; | |
384 | VTUf = min((int)(symbol / diff), 15); | |
385 | calc += symbol / VTUf; | |
386 | } | |
387 | ||
388 | diff = calc - tu_valid; | |
389 | } else { | |
390 | /* no remainder, but the hw doesn't like the fractional | |
391 | * part to be zero. decrement the integer part and | |
392 | * have the fraction add a whole symbol back | |
393 | */ | |
394 | VTUa = 0; | |
395 | VTUf = 1; | |
396 | VTUi--; | |
397 | } | |
398 | ||
399 | if (diff < best_diff) { | |
400 | best_diff = diff; | |
401 | bestTU = TU; | |
402 | bestVTUa = VTUa; | |
403 | bestVTUf = VTUf; | |
404 | bestVTUi = VTUi; | |
405 | if (diff == 0) | |
406 | break; | |
407 | } | |
408 | } | |
409 | ||
8d7ef84d BS |
410 | if (ior->func->dp.activesym) { |
411 | if (!bestTU) { | |
412 | nvkm_error(subdev, "unable to determine dp config\n"); | |
413 | return; | |
414 | } | |
415 | ior->func->dp.activesym(ior, head->id, bestTU, | |
416 | bestVTUa, bestVTUf, bestVTUi); | |
417 | } else { | |
418 | bestTU = 64; | |
186ecad2 BS |
419 | } |
420 | ||
421 | /* XXX close to vbios numbers, but not right */ | |
422 | unk = (symbol - link_ratio) * bestTU; | |
423 | unk *= link_ratio; | |
c354080d BS |
424 | do_div(unk, symbol); |
425 | do_div(unk, symbol); | |
186ecad2 BS |
426 | unk += 6; |
427 | ||
8d7ef84d | 428 | ior->func->dp.watermark(ior, head->id, unk); |
186ecad2 BS |
429 | } |
430 | ||
8d7ef84d BS |
431 | void |
432 | nv50_disp_super_2_2(struct nv50_disp *disp, struct nvkm_head *head) | |
186ecad2 | 433 | { |
8d7ef84d BS |
434 | const u32 khz = head->asy.hz / 1000; |
435 | struct nvkm_outp *outp; | |
436 | struct nvkm_ior *ior; | |
0a0afd28 | 437 | |
8d7ef84d BS |
438 | /* Determine which OR, if any, we're attaching from the head. */ |
439 | HEAD_DBG(head, "supervisor 2.2"); | |
440 | ior = nv50_disp_super_ior_asy(head); | |
441 | if (!ior) | |
415f12ef | 442 | return; |
186ecad2 | 443 | |
8d7ef84d | 444 | /* For some reason, NVIDIA decided not to: |
55f083c3 | 445 | * |
8d7ef84d BS |
446 | * A) Give dual-link LVDS a separate EVO protocol, like for TMDS. |
447 | * and | |
448 | * B) Use SetControlOutputResource.PixelDepth on LVDS. | |
55f083c3 | 449 | * |
8d7ef84d BS |
450 | * Override the values we usually read from HW with the same |
451 | * data we pass though an ioctl instead. | |
55f083c3 | 452 | */ |
8d7ef84d BS |
453 | if (ior->type == SOR && ior->asy.proto == LVDS) { |
454 | head->asy.or.depth = (disp->sor.lvdsconf & 0x0200) ? 24 : 18; | |
455 | ior->asy.link = (disp->sor.lvdsconf & 0x0100) ? 3 : 1; | |
415f12ef BS |
456 | } |
457 | ||
8d7ef84d BS |
458 | /* Handle any link training, etc. */ |
459 | if ((outp = ior->asy.outp) && outp->func->acquire) | |
460 | outp->func->acquire(outp); | |
415f12ef | 461 | |
8d7ef84d BS |
462 | /* Execute OnInt2 IED script. */ |
463 | nv50_disp_super_ied_on(head, ior, 0, khz); | |
464 | ||
465 | /* Program RG clock divider. */ | |
466 | head->func->rgclk(head, ior->asy.rgdiv); | |
415f12ef | 467 | |
8d7ef84d BS |
468 | /* Mode-specific internal DP configuration. */ |
469 | if (ior->type == SOR && ior->asy.proto == DP) | |
470 | nv50_disp_super_2_2_dp(head, ior); | |
2a4bd8ac | 471 | |
8d7ef84d BS |
472 | /* OR-specific handling. */ |
473 | ior->func->clock(ior); | |
474 | if (ior->func->war_2) | |
475 | ior->func->war_2(ior); | |
186ecad2 BS |
476 | } |
477 | ||
1f0c9eaf BS |
478 | void |
479 | nv50_disp_super_2_1(struct nv50_disp *disp, struct nvkm_head *head) | |
186ecad2 | 480 | { |
1f0c9eaf | 481 | struct nvkm_devinit *devinit = disp->base.engine.subdev.device->devinit; |
8d7ef84d | 482 | const u32 khz = head->asy.hz / 1000; |
1f0c9eaf BS |
483 | HEAD_DBG(head, "supervisor 2.1 - %d khz", khz); |
484 | if (khz) | |
485 | nvkm_devinit_pll_set(devinit, PLL_VPLL0 + head->id, khz); | |
186ecad2 BS |
486 | } |
487 | ||
d52e948c BS |
488 | void |
489 | nv50_disp_super_2_0(struct nv50_disp *disp, struct nvkm_head *head) | |
186ecad2 | 490 | { |
d52e948c BS |
491 | struct nvkm_outp *outp; |
492 | struct nvkm_ior *ior; | |
493 | ||
494 | /* Determine which OR, if any, we're detaching from the head. */ | |
495 | HEAD_DBG(head, "supervisor 2.0"); | |
496 | ior = nv50_disp_super_ior_arm(head); | |
497 | if (!ior) | |
498 | return; | |
499 | ||
500 | /* Execute OffInt2 IED script. */ | |
501 | nv50_disp_super_ied_off(head, ior, 2); | |
502 | ||
503 | /* If we're shutting down the OR's only active head, execute | |
e04cfdc9 | 504 | * the output path's disable function. |
af85389c | 505 | */ |
d52e948c | 506 | if (ior->arm.head == (1 << head->id)) { |
e04cfdc9 BS |
507 | if ((outp = ior->arm.outp) && outp->func->disable) |
508 | outp->func->disable(outp, ior); | |
af85389c BS |
509 | } |
510 | } | |
511 | ||
327c5581 BS |
512 | void |
513 | nv50_disp_super_1_0(struct nv50_disp *disp, struct nvkm_head *head) | |
af85389c | 514 | { |
327c5581 BS |
515 | struct nvkm_ior *ior; |
516 | ||
517 | /* Determine which OR, if any, we're detaching from the head. */ | |
518 | HEAD_DBG(head, "supervisor 1.0"); | |
519 | ior = nv50_disp_super_ior_arm(head); | |
520 | if (!ior) | |
521 | return; | |
522 | ||
523 | /* Execute OffInt1 IED script. */ | |
524 | nv50_disp_super_ied_off(head, ior, 1); | |
186ecad2 BS |
525 | } |
526 | ||
29c0ca73 BS |
527 | void |
528 | nv50_disp_super_1(struct nv50_disp *disp) | |
529 | { | |
530 | struct nvkm_head *head; | |
531 | struct nvkm_ior *ior; | |
532 | ||
533 | list_for_each_entry(head, &disp->base.head, head) { | |
534 | head->func->state(head, &head->arm); | |
535 | head->func->state(head, &head->asy); | |
536 | } | |
537 | ||
538 | list_for_each_entry(ior, &disp->base.ior, head) { | |
539 | ior->func->state(ior, &ior->arm); | |
540 | ior->func->state(ior, &ior->asy); | |
541 | } | |
542 | } | |
543 | ||
5cc027f6 | 544 | void |
af85389c | 545 | nv50_disp_super(struct work_struct *work) |
186ecad2 | 546 | { |
fd166a18 BS |
547 | struct nv50_disp *disp = |
548 | container_of(work, struct nv50_disp, supervisor); | |
84407824 BS |
549 | struct nvkm_subdev *subdev = &disp->base.engine.subdev; |
550 | struct nvkm_device *device = subdev->device; | |
a1c93078 | 551 | struct nvkm_head *head; |
2fde1f1c | 552 | u32 super = nvkm_rd32(device, 0x610030); |
186ecad2 | 553 | |
84407824 | 554 | nvkm_debug(subdev, "supervisor %08x %08x\n", disp->super, super); |
186ecad2 | 555 | |
fd166a18 | 556 | if (disp->super & 0x00000010) { |
0ce41e3c | 557 | nv50_disp_chan_mthd(disp->chan[0], NV_DBG_DEBUG); |
29c0ca73 | 558 | nv50_disp_super_1(disp); |
a1c93078 BS |
559 | list_for_each_entry(head, &disp->base.head, head) { |
560 | if (!(super & (0x00000020 << head->id))) | |
16d4c031 | 561 | continue; |
a1c93078 | 562 | if (!(super & (0x00000080 << head->id))) |
16d4c031 | 563 | continue; |
327c5581 | 564 | nv50_disp_super_1_0(disp, head); |
16d4c031 BS |
565 | } |
566 | } else | |
fd166a18 | 567 | if (disp->super & 0x00000020) { |
a1c93078 BS |
568 | list_for_each_entry(head, &disp->base.head, head) { |
569 | if (!(super & (0x00000080 << head->id))) | |
16d4c031 | 570 | continue; |
d52e948c | 571 | nv50_disp_super_2_0(disp, head); |
16d4c031 | 572 | } |
6c22ea37 | 573 | nvkm_outp_route(&disp->base); |
a1c93078 BS |
574 | list_for_each_entry(head, &disp->base.head, head) { |
575 | if (!(super & (0x00000200 << head->id))) | |
16d4c031 | 576 | continue; |
1f0c9eaf | 577 | nv50_disp_super_2_1(disp, head); |
16d4c031 | 578 | } |
a1c93078 BS |
579 | list_for_each_entry(head, &disp->base.head, head) { |
580 | if (!(super & (0x00000080 << head->id))) | |
16d4c031 | 581 | continue; |
8d7ef84d | 582 | nv50_disp_super_2_2(disp, head); |
16d4c031 BS |
583 | } |
584 | } else | |
fd166a18 | 585 | if (disp->super & 0x00000040) { |
a1c93078 BS |
586 | list_for_each_entry(head, &disp->base.head, head) { |
587 | if (!(super & (0x00000080 << head->id))) | |
16d4c031 | 588 | continue; |
0d93cd92 | 589 | nv50_disp_super_3_0(disp, head); |
16d4c031 BS |
590 | } |
591 | } | |
592 | ||
2fde1f1c | 593 | nvkm_wr32(device, 0x610030, 0x80000000); |
186ecad2 BS |
594 | } |
595 | ||
af85389c BS |
596 | static const struct nvkm_enum |
597 | nv50_disp_intr_error_type[] = { | |
598 | { 3, "ILLEGAL_MTHD" }, | |
599 | { 4, "INVALID_VALUE" }, | |
600 | { 5, "INVALID_STATE" }, | |
601 | { 7, "INVALID_HANDLE" }, | |
602 | {} | |
603 | }; | |
604 | ||
605 | static const struct nvkm_enum | |
606 | nv50_disp_intr_error_code[] = { | |
607 | { 0x00, "" }, | |
608 | {} | |
609 | }; | |
610 | ||
611 | static void | |
612 | nv50_disp_intr_error(struct nv50_disp *disp, int chid) | |
613 | { | |
614 | struct nvkm_subdev *subdev = &disp->base.engine.subdev; | |
615 | struct nvkm_device *device = subdev->device; | |
616 | u32 data = nvkm_rd32(device, 0x610084 + (chid * 0x08)); | |
617 | u32 addr = nvkm_rd32(device, 0x610080 + (chid * 0x08)); | |
618 | u32 code = (addr & 0x00ff0000) >> 16; | |
619 | u32 type = (addr & 0x00007000) >> 12; | |
620 | u32 mthd = (addr & 0x00000ffc); | |
621 | const struct nvkm_enum *ec, *et; | |
622 | ||
623 | et = nvkm_enum_find(nv50_disp_intr_error_type, type); | |
624 | ec = nvkm_enum_find(nv50_disp_intr_error_code, code); | |
625 | ||
626 | nvkm_error(subdev, | |
627 | "ERROR %d [%s] %02x [%s] chid %d mthd %04x data %08x\n", | |
628 | type, et ? et->name : "", code, ec ? ec->name : "", | |
629 | chid, mthd, data); | |
630 | ||
631 | if (chid < ARRAY_SIZE(disp->chan)) { | |
632 | switch (mthd) { | |
633 | case 0x0080: | |
634 | nv50_disp_chan_mthd(disp->chan[chid], NV_DBG_ERROR); | |
635 | break; | |
636 | default: | |
637 | break; | |
638 | } | |
639 | } | |
640 | ||
641 | nvkm_wr32(device, 0x610020, 0x00010000 << chid); | |
642 | nvkm_wr32(device, 0x610080 + (chid * 0x08), 0x90000000); | |
643 | } | |
644 | ||
70cabe4a | 645 | void |
70aa8670 | 646 | nv50_disp_intr(struct nv50_disp *disp) |
ebb945a9 | 647 | { |
2fde1f1c BS |
648 | struct nvkm_device *device = disp->base.engine.subdev.device; |
649 | u32 intr0 = nvkm_rd32(device, 0x610020); | |
650 | u32 intr1 = nvkm_rd32(device, 0x610024); | |
ebb945a9 | 651 | |
117e1633 BS |
652 | while (intr0 & 0x001f0000) { |
653 | u32 chid = __ffs(intr0 & 0x001f0000) - 16; | |
fd166a18 | 654 | nv50_disp_intr_error(disp, chid); |
117e1633 | 655 | intr0 &= ~(0x00010000 << chid); |
186ecad2 BS |
656 | } |
657 | ||
b38a2322 BS |
658 | while (intr0 & 0x0000001f) { |
659 | u32 chid = __ffs(intr0 & 0x0000001f); | |
fd166a18 | 660 | nv50_disp_chan_uevent_send(disp, chid); |
b38a2322 BS |
661 | intr0 &= ~(0x00000001 << chid); |
662 | } | |
663 | ||
186ecad2 | 664 | if (intr1 & 0x00000004) { |
fd166a18 | 665 | nvkm_disp_vblank(&disp->base, 0); |
2fde1f1c | 666 | nvkm_wr32(device, 0x610024, 0x00000004); |
ebb945a9 BS |
667 | } |
668 | ||
186ecad2 | 669 | if (intr1 & 0x00000008) { |
fd166a18 | 670 | nvkm_disp_vblank(&disp->base, 1); |
2fde1f1c | 671 | nvkm_wr32(device, 0x610024, 0x00000008); |
ebb945a9 BS |
672 | } |
673 | ||
186ecad2 | 674 | if (intr1 & 0x00000070) { |
fd166a18 | 675 | disp->super = (intr1 & 0x00000070); |
3607bfd3 | 676 | queue_work(disp->wq, &disp->supervisor); |
2fde1f1c | 677 | nvkm_wr32(device, 0x610024, disp->super); |
186ecad2 | 678 | } |
ebb945a9 BS |
679 | } |
680 | ||
bb3b0a42 BS |
681 | void |
682 | nv50_disp_fini(struct nv50_disp *disp) | |
683 | { | |
684 | struct nvkm_device *device = disp->base.engine.subdev.device; | |
685 | /* disable all interrupts */ | |
686 | nvkm_wr32(device, 0x610024, 0x00000000); | |
687 | nvkm_wr32(device, 0x610020, 0x00000000); | |
688 | } | |
689 | ||
690 | int | |
691 | nv50_disp_init(struct nv50_disp *disp) | |
692 | { | |
693 | struct nvkm_device *device = disp->base.engine.subdev.device; | |
694 | struct nvkm_head *head; | |
695 | u32 tmp; | |
696 | int i; | |
697 | ||
698 | /* The below segments of code copying values from one register to | |
699 | * another appear to inform EVO of the display capabilities or | |
700 | * something similar. NFI what the 0x614004 caps are for.. | |
701 | */ | |
702 | tmp = nvkm_rd32(device, 0x614004); | |
703 | nvkm_wr32(device, 0x610184, tmp); | |
704 | ||
705 | /* ... CRTC caps */ | |
706 | list_for_each_entry(head, &disp->base.head, head) { | |
707 | tmp = nvkm_rd32(device, 0x616100 + (head->id * 0x800)); | |
708 | nvkm_wr32(device, 0x610190 + (head->id * 0x10), tmp); | |
709 | tmp = nvkm_rd32(device, 0x616104 + (head->id * 0x800)); | |
710 | nvkm_wr32(device, 0x610194 + (head->id * 0x10), tmp); | |
711 | tmp = nvkm_rd32(device, 0x616108 + (head->id * 0x800)); | |
712 | nvkm_wr32(device, 0x610198 + (head->id * 0x10), tmp); | |
713 | tmp = nvkm_rd32(device, 0x61610c + (head->id * 0x800)); | |
714 | nvkm_wr32(device, 0x61019c + (head->id * 0x10), tmp); | |
715 | } | |
716 | ||
717 | /* ... DAC caps */ | |
718 | for (i = 0; i < disp->dac.nr; i++) { | |
719 | tmp = nvkm_rd32(device, 0x61a000 + (i * 0x800)); | |
720 | nvkm_wr32(device, 0x6101d0 + (i * 0x04), tmp); | |
721 | } | |
722 | ||
723 | /* ... SOR caps */ | |
724 | for (i = 0; i < disp->sor.nr; i++) { | |
725 | tmp = nvkm_rd32(device, 0x61c000 + (i * 0x800)); | |
726 | nvkm_wr32(device, 0x6101e0 + (i * 0x04), tmp); | |
727 | } | |
728 | ||
729 | /* ... PIOR caps */ | |
730 | for (i = 0; i < disp->pior.nr; i++) { | |
731 | tmp = nvkm_rd32(device, 0x61e000 + (i * 0x800)); | |
732 | nvkm_wr32(device, 0x6101f0 + (i * 0x04), tmp); | |
733 | } | |
734 | ||
735 | /* steal display away from vbios, or something like that */ | |
736 | if (nvkm_rd32(device, 0x610024) & 0x00000100) { | |
737 | nvkm_wr32(device, 0x610024, 0x00000100); | |
738 | nvkm_mask(device, 0x6194e8, 0x00000001, 0x00000000); | |
739 | if (nvkm_msec(device, 2000, | |
740 | if (!(nvkm_rd32(device, 0x6194e8) & 0x00000002)) | |
741 | break; | |
742 | ) < 0) | |
743 | return -EBUSY; | |
744 | } | |
745 | ||
746 | /* point at display engine memory area (hash table, objects) */ | |
747 | nvkm_wr32(device, 0x610010, (disp->inst->addr >> 8) | 9); | |
748 | ||
749 | /* enable supervisor interrupts, disable everything else */ | |
750 | nvkm_wr32(device, 0x61002c, 0x00000370); | |
751 | nvkm_wr32(device, 0x610028, 0x00000000); | |
752 | return 0; | |
753 | } | |
754 | ||
70aa8670 | 755 | static const struct nv50_disp_func |
0ce41e3c | 756 | nv50_disp = { |
bb3b0a42 BS |
757 | .init = nv50_disp_init, |
758 | .fini = nv50_disp_fini, | |
70aa8670 BS |
759 | .intr = nv50_disp_intr, |
760 | .uevent = &nv50_disp_chan_uevent, | |
af85389c | 761 | .super = nv50_disp_super, |
0ce41e3c | 762 | .root = &nv50_disp_root_oclass, |
f7b2ece3 | 763 | .head = { .cnt = nv50_head_cnt, .new = nv50_head_new }, |
bf5d1a6b | 764 | .dac = { .cnt = nv50_dac_cnt, .new = nv50_dac_new }, |
9fe4e177 | 765 | .sor = { .cnt = nv50_sor_cnt, .new = nv50_sor_new }, |
f5e088d6 | 766 | .pior = { .cnt = nv50_pior_cnt, .new = nv50_pior_new }, |
0ce41e3c BS |
767 | }; |
768 | ||
70aa8670 BS |
769 | int |
770 | nv50_disp_new(struct nvkm_device *device, int index, struct nvkm_disp **pdisp) | |
ebb945a9 | 771 | { |
f7b2ece3 | 772 | return nv50_disp_new_(&nv50_disp, device, index, pdisp); |
ebb945a9 | 773 | } |