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