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 | */ | |
24 | ||
46654061 BS |
25 | #include <core/object.h> |
26 | #include <core/parent.h> | |
27 | #include <core/handle.h> | |
28 | #include <core/class.h> | |
ebb945a9 BS |
29 | |
30 | #include <engine/software.h> | |
31 | #include <engine/disp.h> | |
32 | ||
46654061 BS |
33 | #include <subdev/timer.h> |
34 | #include <subdev/fb.h> | |
35 | #include <subdev/bar.h> | |
14464b8c BS |
36 | #include <subdev/clock.h> |
37 | ||
38 | #include <subdev/bios.h> | |
39 | #include <subdev/bios/dcb.h> | |
40 | #include <subdev/bios/disp.h> | |
41 | #include <subdev/bios/init.h> | |
42 | #include <subdev/bios/pll.h> | |
46654061 BS |
43 | |
44 | #include "nv50.h" | |
45 | ||
46 | /******************************************************************************* | |
47 | * EVO DMA channel base class | |
48 | ******************************************************************************/ | |
49 | ||
50 | static int | |
51 | nvd0_disp_dmac_object_attach(struct nouveau_object *parent, | |
52 | struct nouveau_object *object, u32 name) | |
53 | { | |
54 | struct nv50_disp_base *base = (void *)parent->parent; | |
55 | struct nv50_disp_chan *chan = (void *)parent; | |
56 | u32 addr = nv_gpuobj(object)->node->offset; | |
57 | u32 data = (chan->chid << 27) | (addr << 9) | 0x00000001; | |
58 | return nouveau_ramht_insert(base->ramht, chan->chid, name, data); | |
59 | } | |
60 | ||
61 | static void | |
62 | nvd0_disp_dmac_object_detach(struct nouveau_object *parent, int cookie) | |
63 | { | |
64 | struct nv50_disp_base *base = (void *)parent->parent; | |
65 | nouveau_ramht_remove(base->ramht, cookie); | |
66 | } | |
67 | ||
68 | static int | |
69 | nvd0_disp_dmac_init(struct nouveau_object *object) | |
70 | { | |
71 | struct nv50_disp_priv *priv = (void *)object->engine; | |
72 | struct nv50_disp_dmac *dmac = (void *)object; | |
73 | int chid = dmac->base.chid; | |
74 | int ret; | |
75 | ||
76 | ret = nv50_disp_chan_init(&dmac->base); | |
77 | if (ret) | |
78 | return ret; | |
79 | ||
80 | /* enable error reporting */ | |
81 | nv_mask(priv, 0x610090, 0x00000001 << chid, 0x00000001 << chid); | |
82 | nv_mask(priv, 0x6100a0, 0x00000001 << chid, 0x00000001 << chid); | |
83 | ||
84 | /* initialise channel for dma command submission */ | |
85 | nv_wr32(priv, 0x610494 + (chid * 0x0010), dmac->push); | |
86 | nv_wr32(priv, 0x610498 + (chid * 0x0010), 0x00010000); | |
87 | nv_wr32(priv, 0x61049c + (chid * 0x0010), 0x00000001); | |
88 | nv_mask(priv, 0x610490 + (chid * 0x0010), 0x00000010, 0x00000010); | |
89 | nv_wr32(priv, 0x640000 + (chid * 0x1000), 0x00000000); | |
90 | nv_wr32(priv, 0x610490 + (chid * 0x0010), 0x00000013); | |
91 | ||
92 | /* wait for it to go inactive */ | |
93 | if (!nv_wait(priv, 0x610490 + (chid * 0x10), 0x80000000, 0x00000000)) { | |
94 | nv_error(dmac, "init: 0x%08x\n", | |
95 | nv_rd32(priv, 0x610490 + (chid * 0x10))); | |
96 | return -EBUSY; | |
97 | } | |
98 | ||
99 | return 0; | |
100 | } | |
101 | ||
102 | static int | |
103 | nvd0_disp_dmac_fini(struct nouveau_object *object, bool suspend) | |
104 | { | |
105 | struct nv50_disp_priv *priv = (void *)object->engine; | |
106 | struct nv50_disp_dmac *dmac = (void *)object; | |
107 | int chid = dmac->base.chid; | |
108 | ||
109 | /* deactivate channel */ | |
110 | nv_mask(priv, 0x610490 + (chid * 0x0010), 0x00001010, 0x00001000); | |
111 | nv_mask(priv, 0x610490 + (chid * 0x0010), 0x00000003, 0x00000000); | |
112 | if (!nv_wait(priv, 0x610490 + (chid * 0x10), 0x001e0000, 0x00000000)) { | |
113 | nv_error(dmac, "fini: 0x%08x\n", | |
114 | nv_rd32(priv, 0x610490 + (chid * 0x10))); | |
115 | if (suspend) | |
116 | return -EBUSY; | |
117 | } | |
118 | ||
119 | /* disable error reporting */ | |
120 | nv_mask(priv, 0x610090, 0x00000001 << chid, 0x00000000); | |
121 | nv_mask(priv, 0x6100a0, 0x00000001 << chid, 0x00000000); | |
122 | ||
123 | return nv50_disp_chan_fini(&dmac->base, suspend); | |
124 | } | |
125 | ||
126 | /******************************************************************************* | |
127 | * EVO master channel object | |
128 | ******************************************************************************/ | |
129 | ||
130 | static int | |
131 | nvd0_disp_mast_ctor(struct nouveau_object *parent, | |
132 | struct nouveau_object *engine, | |
133 | struct nouveau_oclass *oclass, void *data, u32 size, | |
134 | struct nouveau_object **pobject) | |
135 | { | |
136 | struct nv50_display_mast_class *args = data; | |
137 | struct nv50_disp_dmac *mast; | |
138 | int ret; | |
139 | ||
140 | if (size < sizeof(*args)) | |
141 | return -EINVAL; | |
142 | ||
143 | ret = nv50_disp_dmac_create_(parent, engine, oclass, args->pushbuf, | |
144 | 0, sizeof(*mast), (void **)&mast); | |
145 | *pobject = nv_object(mast); | |
146 | if (ret) | |
147 | return ret; | |
148 | ||
149 | nv_parent(mast)->object_attach = nvd0_disp_dmac_object_attach; | |
150 | nv_parent(mast)->object_detach = nvd0_disp_dmac_object_detach; | |
151 | return 0; | |
152 | } | |
153 | ||
154 | static int | |
155 | nvd0_disp_mast_init(struct nouveau_object *object) | |
156 | { | |
157 | struct nv50_disp_priv *priv = (void *)object->engine; | |
158 | struct nv50_disp_dmac *mast = (void *)object; | |
159 | int ret; | |
160 | ||
161 | ret = nv50_disp_chan_init(&mast->base); | |
162 | if (ret) | |
163 | return ret; | |
164 | ||
165 | /* enable error reporting */ | |
166 | nv_mask(priv, 0x610090, 0x00000001, 0x00000001); | |
167 | nv_mask(priv, 0x6100a0, 0x00000001, 0x00000001); | |
168 | ||
169 | /* initialise channel for dma command submission */ | |
170 | nv_wr32(priv, 0x610494, mast->push); | |
171 | nv_wr32(priv, 0x610498, 0x00010000); | |
172 | nv_wr32(priv, 0x61049c, 0x00000001); | |
173 | nv_mask(priv, 0x610490, 0x00000010, 0x00000010); | |
174 | nv_wr32(priv, 0x640000, 0x00000000); | |
175 | nv_wr32(priv, 0x610490, 0x01000013); | |
176 | ||
177 | /* wait for it to go inactive */ | |
178 | if (!nv_wait(priv, 0x610490, 0x80000000, 0x00000000)) { | |
179 | nv_error(mast, "init: 0x%08x\n", nv_rd32(priv, 0x610490)); | |
180 | return -EBUSY; | |
181 | } | |
182 | ||
183 | return 0; | |
184 | } | |
185 | ||
186 | static int | |
187 | nvd0_disp_mast_fini(struct nouveau_object *object, bool suspend) | |
188 | { | |
189 | struct nv50_disp_priv *priv = (void *)object->engine; | |
190 | struct nv50_disp_dmac *mast = (void *)object; | |
191 | ||
192 | /* deactivate channel */ | |
193 | nv_mask(priv, 0x610490, 0x00000010, 0x00000000); | |
194 | nv_mask(priv, 0x610490, 0x00000003, 0x00000000); | |
195 | if (!nv_wait(priv, 0x610490, 0x001e0000, 0x00000000)) { | |
196 | nv_error(mast, "fini: 0x%08x\n", nv_rd32(priv, 0x610490)); | |
197 | if (suspend) | |
198 | return -EBUSY; | |
199 | } | |
200 | ||
201 | /* disable error reporting */ | |
202 | nv_mask(priv, 0x610090, 0x00000001, 0x00000000); | |
203 | nv_mask(priv, 0x6100a0, 0x00000001, 0x00000000); | |
204 | ||
205 | return nv50_disp_chan_fini(&mast->base, suspend); | |
206 | } | |
207 | ||
208 | struct nouveau_ofuncs | |
209 | nvd0_disp_mast_ofuncs = { | |
210 | .ctor = nvd0_disp_mast_ctor, | |
211 | .dtor = nv50_disp_dmac_dtor, | |
212 | .init = nvd0_disp_mast_init, | |
213 | .fini = nvd0_disp_mast_fini, | |
214 | .rd32 = nv50_disp_chan_rd32, | |
215 | .wr32 = nv50_disp_chan_wr32, | |
216 | }; | |
217 | ||
218 | /******************************************************************************* | |
219 | * EVO sync channel objects | |
220 | ******************************************************************************/ | |
221 | ||
222 | static int | |
223 | nvd0_disp_sync_ctor(struct nouveau_object *parent, | |
224 | struct nouveau_object *engine, | |
225 | struct nouveau_oclass *oclass, void *data, u32 size, | |
226 | struct nouveau_object **pobject) | |
227 | { | |
228 | struct nv50_display_sync_class *args = data; | |
229 | struct nv50_disp_priv *priv = (void *)engine; | |
230 | struct nv50_disp_dmac *dmac; | |
231 | int ret; | |
232 | ||
233 | if (size < sizeof(*data) || args->head >= priv->head.nr) | |
234 | return -EINVAL; | |
235 | ||
236 | ret = nv50_disp_dmac_create_(parent, engine, oclass, args->pushbuf, | |
237 | 1 + args->head, sizeof(*dmac), | |
238 | (void **)&dmac); | |
239 | *pobject = nv_object(dmac); | |
240 | if (ret) | |
241 | return ret; | |
242 | ||
243 | nv_parent(dmac)->object_attach = nvd0_disp_dmac_object_attach; | |
244 | nv_parent(dmac)->object_detach = nvd0_disp_dmac_object_detach; | |
245 | return 0; | |
246 | } | |
247 | ||
248 | struct nouveau_ofuncs | |
249 | nvd0_disp_sync_ofuncs = { | |
250 | .ctor = nvd0_disp_sync_ctor, | |
251 | .dtor = nv50_disp_dmac_dtor, | |
252 | .init = nvd0_disp_dmac_init, | |
253 | .fini = nvd0_disp_dmac_fini, | |
254 | .rd32 = nv50_disp_chan_rd32, | |
255 | .wr32 = nv50_disp_chan_wr32, | |
256 | }; | |
257 | ||
258 | /******************************************************************************* | |
259 | * EVO overlay channel objects | |
260 | ******************************************************************************/ | |
261 | ||
262 | static int | |
263 | nvd0_disp_ovly_ctor(struct nouveau_object *parent, | |
264 | struct nouveau_object *engine, | |
265 | struct nouveau_oclass *oclass, void *data, u32 size, | |
266 | struct nouveau_object **pobject) | |
267 | { | |
268 | struct nv50_display_ovly_class *args = data; | |
269 | struct nv50_disp_priv *priv = (void *)engine; | |
270 | struct nv50_disp_dmac *dmac; | |
271 | int ret; | |
272 | ||
273 | if (size < sizeof(*data) || args->head >= priv->head.nr) | |
274 | return -EINVAL; | |
275 | ||
276 | ret = nv50_disp_dmac_create_(parent, engine, oclass, args->pushbuf, | |
277 | 5 + args->head, sizeof(*dmac), | |
278 | (void **)&dmac); | |
279 | *pobject = nv_object(dmac); | |
280 | if (ret) | |
281 | return ret; | |
282 | ||
283 | nv_parent(dmac)->object_attach = nvd0_disp_dmac_object_attach; | |
284 | nv_parent(dmac)->object_detach = nvd0_disp_dmac_object_detach; | |
285 | return 0; | |
286 | } | |
287 | ||
288 | struct nouveau_ofuncs | |
289 | nvd0_disp_ovly_ofuncs = { | |
290 | .ctor = nvd0_disp_ovly_ctor, | |
291 | .dtor = nv50_disp_dmac_dtor, | |
292 | .init = nvd0_disp_dmac_init, | |
293 | .fini = nvd0_disp_dmac_fini, | |
294 | .rd32 = nv50_disp_chan_rd32, | |
295 | .wr32 = nv50_disp_chan_wr32, | |
296 | }; | |
297 | ||
298 | /******************************************************************************* | |
299 | * EVO PIO channel base class | |
300 | ******************************************************************************/ | |
301 | ||
302 | static int | |
303 | nvd0_disp_pioc_create_(struct nouveau_object *parent, | |
304 | struct nouveau_object *engine, | |
305 | struct nouveau_oclass *oclass, int chid, | |
306 | int length, void **pobject) | |
307 | { | |
308 | return nv50_disp_chan_create_(parent, engine, oclass, chid, | |
309 | length, pobject); | |
310 | } | |
311 | ||
312 | static void | |
313 | nvd0_disp_pioc_dtor(struct nouveau_object *object) | |
314 | { | |
315 | struct nv50_disp_pioc *pioc = (void *)object; | |
316 | nv50_disp_chan_destroy(&pioc->base); | |
317 | } | |
318 | ||
319 | static int | |
320 | nvd0_disp_pioc_init(struct nouveau_object *object) | |
321 | { | |
322 | struct nv50_disp_priv *priv = (void *)object->engine; | |
323 | struct nv50_disp_pioc *pioc = (void *)object; | |
324 | int chid = pioc->base.chid; | |
325 | int ret; | |
326 | ||
327 | ret = nv50_disp_chan_init(&pioc->base); | |
328 | if (ret) | |
329 | return ret; | |
330 | ||
331 | /* enable error reporting */ | |
332 | nv_mask(priv, 0x610090, 0x00000001 << chid, 0x00000001 << chid); | |
333 | nv_mask(priv, 0x6100a0, 0x00000001 << chid, 0x00000001 << chid); | |
334 | ||
335 | /* activate channel */ | |
336 | nv_wr32(priv, 0x610490 + (chid * 0x10), 0x00000001); | |
337 | if (!nv_wait(priv, 0x610490 + (chid * 0x10), 0x00030000, 0x00010000)) { | |
338 | nv_error(pioc, "init: 0x%08x\n", | |
339 | nv_rd32(priv, 0x610490 + (chid * 0x10))); | |
340 | return -EBUSY; | |
341 | } | |
342 | ||
343 | return 0; | |
344 | } | |
345 | ||
346 | static int | |
347 | nvd0_disp_pioc_fini(struct nouveau_object *object, bool suspend) | |
348 | { | |
349 | struct nv50_disp_priv *priv = (void *)object->engine; | |
350 | struct nv50_disp_pioc *pioc = (void *)object; | |
351 | int chid = pioc->base.chid; | |
352 | ||
353 | nv_mask(priv, 0x610490 + (chid * 0x10), 0x00000001, 0x00000000); | |
354 | if (!nv_wait(priv, 0x610490 + (chid * 0x10), 0x00030000, 0x00000000)) { | |
355 | nv_error(pioc, "timeout: 0x%08x\n", | |
356 | nv_rd32(priv, 0x610490 + (chid * 0x10))); | |
357 | if (suspend) | |
358 | return -EBUSY; | |
359 | } | |
360 | ||
361 | /* disable error reporting */ | |
362 | nv_mask(priv, 0x610090, 0x00000001 << chid, 0x00000000); | |
363 | nv_mask(priv, 0x6100a0, 0x00000001 << chid, 0x00000000); | |
364 | ||
365 | return nv50_disp_chan_fini(&pioc->base, suspend); | |
366 | } | |
367 | ||
368 | /******************************************************************************* | |
369 | * EVO immediate overlay channel objects | |
370 | ******************************************************************************/ | |
371 | ||
372 | static int | |
373 | nvd0_disp_oimm_ctor(struct nouveau_object *parent, | |
374 | struct nouveau_object *engine, | |
375 | struct nouveau_oclass *oclass, void *data, u32 size, | |
376 | struct nouveau_object **pobject) | |
377 | { | |
378 | struct nv50_display_oimm_class *args = data; | |
379 | struct nv50_disp_priv *priv = (void *)engine; | |
380 | struct nv50_disp_pioc *pioc; | |
381 | int ret; | |
382 | ||
383 | if (size < sizeof(*args) || args->head >= priv->head.nr) | |
384 | return -EINVAL; | |
385 | ||
386 | ret = nvd0_disp_pioc_create_(parent, engine, oclass, 9 + args->head, | |
387 | sizeof(*pioc), (void **)&pioc); | |
388 | *pobject = nv_object(pioc); | |
389 | if (ret) | |
390 | return ret; | |
391 | ||
392 | return 0; | |
393 | } | |
394 | ||
395 | struct nouveau_ofuncs | |
396 | nvd0_disp_oimm_ofuncs = { | |
397 | .ctor = nvd0_disp_oimm_ctor, | |
398 | .dtor = nvd0_disp_pioc_dtor, | |
399 | .init = nvd0_disp_pioc_init, | |
400 | .fini = nvd0_disp_pioc_fini, | |
401 | .rd32 = nv50_disp_chan_rd32, | |
402 | .wr32 = nv50_disp_chan_wr32, | |
403 | }; | |
404 | ||
405 | /******************************************************************************* | |
406 | * EVO cursor channel objects | |
407 | ******************************************************************************/ | |
408 | ||
409 | static int | |
410 | nvd0_disp_curs_ctor(struct nouveau_object *parent, | |
411 | struct nouveau_object *engine, | |
412 | struct nouveau_oclass *oclass, void *data, u32 size, | |
413 | struct nouveau_object **pobject) | |
414 | { | |
415 | struct nv50_display_curs_class *args = data; | |
416 | struct nv50_disp_priv *priv = (void *)engine; | |
417 | struct nv50_disp_pioc *pioc; | |
418 | int ret; | |
419 | ||
420 | if (size < sizeof(*args) || args->head >= priv->head.nr) | |
421 | return -EINVAL; | |
422 | ||
423 | ret = nvd0_disp_pioc_create_(parent, engine, oclass, 13 + args->head, | |
424 | sizeof(*pioc), (void **)&pioc); | |
425 | *pobject = nv_object(pioc); | |
426 | if (ret) | |
427 | return ret; | |
428 | ||
429 | return 0; | |
430 | } | |
431 | ||
432 | struct nouveau_ofuncs | |
433 | nvd0_disp_curs_ofuncs = { | |
434 | .ctor = nvd0_disp_curs_ctor, | |
435 | .dtor = nvd0_disp_pioc_dtor, | |
436 | .init = nvd0_disp_pioc_init, | |
437 | .fini = nvd0_disp_pioc_fini, | |
438 | .rd32 = nv50_disp_chan_rd32, | |
439 | .wr32 = nv50_disp_chan_wr32, | |
440 | }; | |
441 | ||
442 | /******************************************************************************* | |
443 | * Base display object | |
444 | ******************************************************************************/ | |
445 | ||
446 | static int | |
447 | nvd0_disp_base_ctor(struct nouveau_object *parent, | |
448 | struct nouveau_object *engine, | |
449 | struct nouveau_oclass *oclass, void *data, u32 size, | |
450 | struct nouveau_object **pobject) | |
451 | { | |
452 | struct nv50_disp_priv *priv = (void *)engine; | |
453 | struct nv50_disp_base *base; | |
454 | int ret; | |
455 | ||
456 | ret = nouveau_parent_create(parent, engine, oclass, 0, | |
457 | priv->sclass, 0, &base); | |
458 | *pobject = nv_object(base); | |
459 | if (ret) | |
460 | return ret; | |
461 | ||
462 | return nouveau_ramht_new(parent, parent, 0x1000, 0, &base->ramht); | |
463 | } | |
464 | ||
465 | static void | |
466 | nvd0_disp_base_dtor(struct nouveau_object *object) | |
467 | { | |
468 | struct nv50_disp_base *base = (void *)object; | |
469 | nouveau_ramht_ref(NULL, &base->ramht); | |
470 | nouveau_parent_destroy(&base->base); | |
471 | } | |
472 | ||
473 | static int | |
474 | nvd0_disp_base_init(struct nouveau_object *object) | |
475 | { | |
476 | struct nv50_disp_priv *priv = (void *)object->engine; | |
477 | struct nv50_disp_base *base = (void *)object; | |
478 | int ret, i; | |
479 | u32 tmp; | |
480 | ||
481 | ret = nouveau_parent_init(&base->base); | |
482 | if (ret) | |
483 | return ret; | |
484 | ||
485 | /* The below segments of code copying values from one register to | |
486 | * another appear to inform EVO of the display capabilities or | |
487 | * something similar. | |
488 | */ | |
489 | ||
490 | /* ... CRTC caps */ | |
491 | for (i = 0; i < priv->head.nr; i++) { | |
492 | tmp = nv_rd32(priv, 0x616104 + (i * 0x800)); | |
493 | nv_wr32(priv, 0x6101b4 + (i * 0x800), tmp); | |
494 | tmp = nv_rd32(priv, 0x616108 + (i * 0x800)); | |
495 | nv_wr32(priv, 0x6101b8 + (i * 0x800), tmp); | |
496 | tmp = nv_rd32(priv, 0x61610c + (i * 0x800)); | |
497 | nv_wr32(priv, 0x6101bc + (i * 0x800), tmp); | |
498 | } | |
499 | ||
500 | /* ... DAC caps */ | |
501 | for (i = 0; i < priv->dac.nr; i++) { | |
502 | tmp = nv_rd32(priv, 0x61a000 + (i * 0x800)); | |
503 | nv_wr32(priv, 0x6101c0 + (i * 0x800), tmp); | |
504 | } | |
505 | ||
506 | /* ... SOR caps */ | |
507 | for (i = 0; i < priv->sor.nr; i++) { | |
508 | tmp = nv_rd32(priv, 0x61c000 + (i * 0x800)); | |
509 | nv_wr32(priv, 0x6301c4 + (i * 0x800), tmp); | |
510 | } | |
511 | ||
512 | /* steal display away from vbios, or something like that */ | |
513 | if (nv_rd32(priv, 0x6100ac) & 0x00000100) { | |
514 | nv_wr32(priv, 0x6100ac, 0x00000100); | |
515 | nv_mask(priv, 0x6194e8, 0x00000001, 0x00000000); | |
516 | if (!nv_wait(priv, 0x6194e8, 0x00000002, 0x00000000)) { | |
517 | nv_error(priv, "timeout acquiring display\n"); | |
518 | return -EBUSY; | |
519 | } | |
520 | } | |
521 | ||
522 | /* point at display engine memory area (hash table, objects) */ | |
523 | nv_wr32(priv, 0x610010, (nv_gpuobj(object->parent)->addr >> 8) | 9); | |
524 | ||
525 | /* enable supervisor interrupts, disable everything else */ | |
526 | nv_wr32(priv, 0x610090, 0x00000000); | |
527 | nv_wr32(priv, 0x6100a0, 0x00000000); | |
528 | nv_wr32(priv, 0x6100b0, 0x00000307); | |
529 | ||
530 | return 0; | |
531 | } | |
532 | ||
533 | static int | |
534 | nvd0_disp_base_fini(struct nouveau_object *object, bool suspend) | |
535 | { | |
536 | struct nv50_disp_priv *priv = (void *)object->engine; | |
537 | struct nv50_disp_base *base = (void *)object; | |
538 | ||
539 | /* disable all interrupts */ | |
540 | nv_wr32(priv, 0x6100b0, 0x00000000); | |
541 | ||
542 | return nouveau_parent_fini(&base->base, suspend); | |
543 | } | |
544 | ||
545 | struct nouveau_ofuncs | |
546 | nvd0_disp_base_ofuncs = { | |
547 | .ctor = nvd0_disp_base_ctor, | |
548 | .dtor = nvd0_disp_base_dtor, | |
549 | .init = nvd0_disp_base_init, | |
550 | .fini = nvd0_disp_base_fini, | |
551 | }; | |
552 | ||
553 | static struct nouveau_oclass | |
554 | nvd0_disp_base_oclass[] = { | |
6c5a0424 | 555 | { NVD0_DISP_CLASS, &nvd0_disp_base_ofuncs, nva3_disp_base_omthds }, |
46654061 | 556 | {} |
ebb945a9 BS |
557 | }; |
558 | ||
559 | static struct nouveau_oclass | |
560 | nvd0_disp_sclass[] = { | |
46654061 BS |
561 | { NVD0_DISP_MAST_CLASS, &nvd0_disp_mast_ofuncs }, |
562 | { NVD0_DISP_SYNC_CLASS, &nvd0_disp_sync_ofuncs }, | |
563 | { NVD0_DISP_OVLY_CLASS, &nvd0_disp_ovly_ofuncs }, | |
564 | { NVD0_DISP_OIMM_CLASS, &nvd0_disp_oimm_ofuncs }, | |
565 | { NVD0_DISP_CURS_CLASS, &nvd0_disp_curs_ofuncs }, | |
566 | {} | |
ebb945a9 BS |
567 | }; |
568 | ||
46654061 BS |
569 | /******************************************************************************* |
570 | * Display engine implementation | |
571 | ******************************************************************************/ | |
572 | ||
14464b8c BS |
573 | static u16 |
574 | exec_lookup(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, | |
575 | struct dcb_output *dcb, u8 *ver, u8 *hdr, u8 *cnt, u8 *len, | |
576 | struct nvbios_outp *info) | |
577 | { | |
578 | struct nouveau_bios *bios = nouveau_bios(priv); | |
579 | u16 data, idx = 0; | |
580 | u16 mask, type; | |
581 | ||
582 | if (outp < 4) { | |
583 | type = DCB_OUTPUT_ANALOG; | |
584 | mask = 0; | |
585 | } else { | |
586 | outp -= 4; | |
587 | switch (ctrl & 0x00000f00) { | |
588 | case 0x00000000: type = DCB_OUTPUT_LVDS; mask = 1; break; | |
589 | case 0x00000100: type = DCB_OUTPUT_TMDS; mask = 1; break; | |
590 | case 0x00000200: type = DCB_OUTPUT_TMDS; mask = 2; break; | |
591 | case 0x00000500: type = DCB_OUTPUT_TMDS; mask = 3; break; | |
592 | case 0x00000800: type = DCB_OUTPUT_DP; mask = 1; break; | |
593 | case 0x00000900: type = DCB_OUTPUT_DP; mask = 2; break; | |
594 | default: | |
595 | nv_error(priv, "unknown SOR mc 0x%08x\n", ctrl); | |
596 | return 0x0000; | |
597 | } | |
598 | dcb->sorconf.link = mask; | |
599 | } | |
600 | ||
601 | mask = 0x00c0 & (mask << 6); | |
602 | mask |= 0x0001 << outp; | |
603 | mask |= 0x0100 << head; | |
604 | ||
605 | /* this is a tad special, but for the moment its needed to get | |
606 | * all the dcb data required by the vbios scripts.. will be cleaned | |
607 | * up later as more bits are moved to the core.. | |
608 | */ | |
609 | while ((data = dcb_outp(bios, idx++, ver, hdr))) { | |
610 | u32 conn = nv_ro32(bios, data + 0); | |
611 | u32 conf = nv_ro32(bios, data + 4); | |
612 | if ((conn & 0x00300000) || | |
613 | (conn & 0x0000000f) != type || | |
614 | (conn & 0x0f000000) != (0x01000000 << outp)) | |
615 | continue; | |
616 | ||
617 | if ( (mask & 0x00c0) && (mask & 0x00c0) != | |
618 | ((mask & 0x00c0) & ((conf & 0x00000030) << 2))) | |
619 | continue; | |
620 | ||
621 | dcb->type = type; | |
622 | dcb->or = 1 << outp; | |
623 | dcb->connector = (conn & 0x0000f000) >> 12; | |
624 | ||
625 | return nvbios_outp_match(bios, type, mask, ver, hdr, cnt, len, info); | |
626 | } | |
627 | ||
628 | return 0x0000; | |
629 | } | |
630 | ||
631 | static bool | |
632 | exec_script(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, int id) | |
633 | { | |
634 | struct nouveau_bios *bios = nouveau_bios(priv); | |
635 | struct nvbios_outp info; | |
636 | struct dcb_output dcb; | |
637 | u8 ver, hdr, cnt, len; | |
638 | u16 data; | |
639 | ||
640 | data = exec_lookup(priv, head, outp, ctrl, &dcb, &ver, &hdr, &cnt, &len, &info); | |
641 | if (data) { | |
642 | struct nvbios_init init = { | |
643 | .subdev = nv_subdev(priv), | |
644 | .bios = bios, | |
645 | .offset = info.script[id], | |
646 | .outp = &dcb, | |
647 | .crtc = head, | |
648 | .execute = 1, | |
649 | }; | |
650 | ||
651 | return nvbios_exec(&init) == 0; | |
652 | } | |
653 | ||
654 | return false; | |
655 | } | |
656 | ||
657 | static bool | |
658 | exec_clkcmp(struct nv50_disp_priv *priv, int head, int outp, | |
659 | u32 ctrl, u32 conf, int id, u32 pclk) | |
660 | { | |
661 | struct nouveau_bios *bios = nouveau_bios(priv); | |
662 | struct nvbios_outp info1; | |
663 | struct nvbios_ocfg info2; | |
664 | struct dcb_output dcb; | |
665 | u8 ver, hdr, cnt, len; | |
666 | u16 data; | |
667 | ||
668 | data = exec_lookup(priv, head, outp, ctrl, &dcb, &ver, &hdr, &cnt, &len, &info1); | |
669 | if (data) { | |
670 | data = nvbios_ocfg_match(bios, data, conf, &ver, &hdr, &cnt, &len, &info2); | |
671 | if (data) { | |
672 | data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk); | |
673 | if (data) { | |
674 | struct nvbios_init init = { | |
675 | .subdev = nv_subdev(priv), | |
676 | .bios = bios, | |
677 | .offset = data, | |
678 | .outp = &dcb, | |
679 | .crtc = head, | |
680 | .execute = 1, | |
681 | }; | |
682 | ||
683 | return nvbios_exec(&init) == 0; | |
684 | } | |
685 | } | |
686 | } | |
687 | ||
688 | return false; | |
689 | } | |
690 | ||
691 | static void | |
692 | nvd0_display_unk1_handler(struct nv50_disp_priv *priv, u32 head, u32 mask) | |
693 | { | |
694 | int i; | |
695 | ||
696 | for (i = 0; mask && i < 8; i++) { | |
697 | u32 mcc = nv_rd32(priv, 0x640180 + (i * 0x20)); | |
698 | if (mcc & (1 << head)) | |
699 | exec_script(priv, head, i, mcc, 1); | |
700 | } | |
701 | ||
702 | nv_wr32(priv, 0x6101d4, 0x00000000); | |
703 | nv_wr32(priv, 0x6109d4, 0x00000000); | |
704 | nv_wr32(priv, 0x6101d0, 0x80000000); | |
705 | } | |
706 | ||
707 | static void | |
708 | nvd0_display_unk2_handler(struct nv50_disp_priv *priv, u32 head, u32 mask) | |
709 | { | |
710 | u32 pclk; | |
711 | int i; | |
712 | ||
713 | for (i = 0; mask && i < 8; i++) { | |
714 | u32 mcc = nv_rd32(priv, 0x640180 + (i * 0x20)); | |
715 | if (mcc & (1 << head)) | |
716 | exec_script(priv, head, i, mcc, 2); | |
717 | } | |
718 | ||
719 | pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; | |
720 | nv_debug(priv, "head %d pclk %d mask 0x%08x\n", head, pclk, mask); | |
721 | if (pclk && (mask & 0x00010000)) { | |
722 | struct nouveau_clock *clk = nouveau_clock(priv); | |
723 | clk->pll_set(clk, PLL_VPLL0 + head, pclk); | |
724 | } | |
725 | ||
726 | nv_wr32(priv, 0x612200 + (head * 0x800), 0x00000000); | |
727 | ||
728 | for (i = 0; mask && i < 8; i++) { | |
729 | u32 mcp = nv_rd32(priv, 0x660180 + (i * 0x20)); | |
730 | u32 cfg = nv_rd32(priv, 0x660184 + (i * 0x20)); | |
731 | if (mcp & (1 << head)) { | |
732 | if (exec_clkcmp(priv, head, i, mcp, cfg, 0, pclk)) { | |
733 | u32 addr, mask, data = 0x00000000; | |
734 | if (i < 4) { | |
735 | addr = 0x612280 + ((i - 0) * 0x800); | |
736 | mask = 0xffffffff; | |
737 | } else { | |
738 | addr = 0x612300 + ((i - 4) * 0x800); | |
739 | mask = 0x00000707; | |
740 | if (cfg & 0x00000100) | |
741 | data = 0x00000101; | |
742 | } | |
743 | nv_mask(priv, addr, mask, data); | |
744 | } | |
745 | break; | |
746 | } | |
747 | } | |
748 | ||
749 | nv_wr32(priv, 0x6101d4, 0x00000000); | |
750 | nv_wr32(priv, 0x6109d4, 0x00000000); | |
751 | nv_wr32(priv, 0x6101d0, 0x80000000); | |
752 | } | |
753 | ||
754 | static void | |
755 | nvd0_display_unk4_handler(struct nv50_disp_priv *priv, u32 head, u32 mask) | |
756 | { | |
757 | int pclk, i; | |
758 | ||
759 | pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; | |
760 | ||
761 | for (i = 0; mask && i < 8; i++) { | |
762 | u32 mcp = nv_rd32(priv, 0x660180 + (i * 0x20)); | |
763 | u32 cfg = nv_rd32(priv, 0x660184 + (i * 0x20)); | |
764 | if (mcp & (1 << head)) | |
765 | exec_clkcmp(priv, head, i, mcp, cfg, 1, pclk); | |
766 | } | |
767 | ||
768 | nv_wr32(priv, 0x6101d4, 0x00000000); | |
769 | nv_wr32(priv, 0x6109d4, 0x00000000); | |
770 | nv_wr32(priv, 0x6101d0, 0x80000000); | |
771 | } | |
772 | ||
ebb945a9 | 773 | static void |
46654061 | 774 | nvd0_disp_intr_vblank(struct nv50_disp_priv *priv, int crtc) |
ebb945a9 BS |
775 | { |
776 | struct nouveau_bar *bar = nouveau_bar(priv); | |
777 | struct nouveau_disp *disp = &priv->base; | |
778 | struct nouveau_software_chan *chan, *temp; | |
779 | unsigned long flags; | |
780 | ||
781 | spin_lock_irqsave(&disp->vblank.lock, flags); | |
782 | list_for_each_entry_safe(chan, temp, &disp->vblank.list, vblank.head) { | |
783 | if (chan->vblank.crtc != crtc) | |
784 | continue; | |
785 | ||
786 | nv_wr32(priv, 0x001718, 0x80000000 | chan->vblank.channel); | |
787 | bar->flush(bar); | |
788 | nv_wr32(priv, 0x06000c, upper_32_bits(chan->vblank.offset)); | |
789 | nv_wr32(priv, 0x060010, lower_32_bits(chan->vblank.offset)); | |
790 | nv_wr32(priv, 0x060014, chan->vblank.value); | |
791 | ||
792 | list_del(&chan->vblank.head); | |
793 | if (disp->vblank.put) | |
794 | disp->vblank.put(disp->vblank.data, crtc); | |
795 | } | |
796 | spin_unlock_irqrestore(&disp->vblank.lock, flags); | |
797 | ||
798 | if (disp->vblank.notify) | |
799 | disp->vblank.notify(disp->vblank.data, crtc); | |
800 | } | |
801 | ||
46654061 | 802 | void |
ebb945a9 BS |
803 | nvd0_disp_intr(struct nouveau_subdev *subdev) |
804 | { | |
46654061 | 805 | struct nv50_disp_priv *priv = (void *)subdev; |
ebb945a9 BS |
806 | u32 intr = nv_rd32(priv, 0x610088); |
807 | int i; | |
808 | ||
14464b8c BS |
809 | if (intr & 0x00000001) { |
810 | u32 stat = nv_rd32(priv, 0x61008c); | |
811 | nv_wr32(priv, 0x61008c, stat); | |
812 | intr &= ~0x00000001; | |
813 | } | |
814 | ||
815 | if (intr & 0x00000002) { | |
816 | u32 stat = nv_rd32(priv, 0x61009c); | |
817 | int chid = ffs(stat) - 1; | |
818 | if (chid >= 0) { | |
819 | u32 mthd = nv_rd32(priv, 0x6101f0 + (chid * 12)); | |
820 | u32 data = nv_rd32(priv, 0x6101f4 + (chid * 12)); | |
821 | u32 unkn = nv_rd32(priv, 0x6101f8 + (chid * 12)); | |
822 | ||
823 | nv_error(priv, "chid %d mthd 0x%04x data 0x%08x " | |
824 | "0x%08x 0x%08x\n", | |
825 | chid, (mthd & 0x0000ffc), data, mthd, unkn); | |
826 | nv_wr32(priv, 0x61009c, (1 << chid)); | |
827 | nv_wr32(priv, 0x6101f0 + (chid * 12), 0x90000000); | |
828 | } | |
829 | ||
830 | intr &= ~0x00000002; | |
831 | } | |
832 | ||
833 | if (intr & 0x00100000) { | |
834 | u32 stat = nv_rd32(priv, 0x6100ac); | |
835 | u32 mask = 0, crtc = ~0; | |
836 | ||
837 | while (!mask && ++crtc < priv->head.nr) | |
838 | mask = nv_rd32(priv, 0x6101d4 + (crtc * 0x800)); | |
839 | ||
840 | if (stat & 0x00000001) { | |
841 | nv_wr32(priv, 0x6100ac, 0x00000001); | |
842 | nvd0_display_unk1_handler(priv, crtc, mask); | |
843 | stat &= ~0x00000001; | |
844 | } | |
845 | ||
846 | if (stat & 0x00000002) { | |
847 | nv_wr32(priv, 0x6100ac, 0x00000002); | |
848 | nvd0_display_unk2_handler(priv, crtc, mask); | |
849 | stat &= ~0x00000002; | |
850 | } | |
851 | ||
852 | if (stat & 0x00000004) { | |
853 | nv_wr32(priv, 0x6100ac, 0x00000004); | |
854 | nvd0_display_unk4_handler(priv, crtc, mask); | |
855 | stat &= ~0x00000004; | |
856 | } | |
857 | ||
858 | if (stat) { | |
859 | nv_info(priv, "unknown intr24 0x%08x\n", stat); | |
860 | nv_wr32(priv, 0x6100ac, stat); | |
861 | } | |
862 | ||
863 | intr &= ~0x00100000; | |
864 | } | |
865 | ||
866 | for (i = 0; i < priv->head.nr; i++) { | |
ebb945a9 BS |
867 | u32 mask = 0x01000000 << i; |
868 | if (mask & intr) { | |
869 | u32 stat = nv_rd32(priv, 0x6100bc + (i * 0x800)); | |
870 | if (stat & 0x00000001) | |
871 | nvd0_disp_intr_vblank(priv, i); | |
872 | nv_mask(priv, 0x6100bc + (i * 0x800), 0, 0); | |
873 | nv_rd32(priv, 0x6100c0 + (i * 0x800)); | |
874 | } | |
875 | } | |
876 | } | |
877 | ||
878 | static int | |
879 | nvd0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, | |
46654061 BS |
880 | struct nouveau_oclass *oclass, void *data, u32 size, |
881 | struct nouveau_object **pobject) | |
ebb945a9 | 882 | { |
46654061 | 883 | struct nv50_disp_priv *priv; |
ebb945a9 BS |
884 | int ret; |
885 | ||
886 | ret = nouveau_disp_create(parent, engine, oclass, "PDISP", | |
887 | "display", &priv); | |
888 | *pobject = nv_object(priv); | |
889 | if (ret) | |
890 | return ret; | |
891 | ||
46654061 BS |
892 | nv_engine(priv)->sclass = nvd0_disp_base_oclass; |
893 | nv_engine(priv)->cclass = &nv50_disp_cclass; | |
ebb945a9 | 894 | nv_subdev(priv)->intr = nvd0_disp_intr; |
46654061 BS |
895 | priv->sclass = nvd0_disp_sclass; |
896 | priv->head.nr = nv_rd32(priv, 0x022448); | |
897 | priv->dac.nr = 3; | |
898 | priv->sor.nr = 4; | |
35b21d39 BS |
899 | priv->dac.power = nv50_dac_power; |
900 | priv->dac.sense = nv50_dac_sense; | |
74b66850 | 901 | priv->sor.power = nv50_sor_power; |
0a9e2b95 | 902 | priv->sor.hda_eld = nvd0_hda_eld; |
6c5a0424 BS |
903 | priv->sor.dp_train = nvd0_sor_dp_train; |
904 | priv->sor.dp_lnkctl = nvd0_sor_dp_lnkctl; | |
905 | priv->sor.dp_drvctl = nvd0_sor_dp_drvctl; | |
ebb945a9 BS |
906 | |
907 | INIT_LIST_HEAD(&priv->base.vblank.list); | |
908 | spin_lock_init(&priv->base.vblank.lock); | |
909 | return 0; | |
910 | } | |
911 | ||
912 | struct nouveau_oclass | |
913 | nvd0_disp_oclass = { | |
46654061 | 914 | .handle = NV_ENGINE(DISP, 0x90), |
ebb945a9 BS |
915 | .ofuncs = &(struct nouveau_ofuncs) { |
916 | .ctor = nvd0_disp_ctor, | |
917 | .dtor = _nouveau_disp_dtor, | |
918 | .init = _nouveau_disp_init, | |
919 | .fini = _nouveau_disp_fini, | |
920 | }, | |
921 | }; |