NV50: Deobfuscate some of the modesetting, a few things were hidden away in innocent...
[nouveau] / src / nv50_sor.c
1 /*
2  * Copyright (c) 2007 NVIDIA, Corporation
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the
6  * "Software"), to deal in the Software without restriction, including
7  * without limitation the rights to use, copy, modify, merge, publish,
8  * distribute, sublicense, and/or sell copies of the Software, and to
9  * permit persons to whom the Software is furnished to do so, subject to
10  * the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included
13  * in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22  */
23
24 #define DPMS_SERVER
25 #include <X11/extensions/dpms.h>
26 #include <X11/Xatom.h>
27
28 #include "nv_include.h"
29 #include "nv50_type.h"
30 #include "nv50_display.h"
31 #include "nv50_output.h"
32
33 void
34 NV50SorSetPClk(xf86OutputPtr output, int pclk)
35 {
36         ScrnInfoPtr pScrn = output->scrn;
37         NVPtr pNv = NVPTR(pScrn);
38         const int limit = 165000;
39
40         NVWrite(pNv, 0x00614300 + NV50OrOffset(output) * 0x800, (pclk > limit) ? 0x101 : 0);
41 }
42
43 static void
44 NV50SorDPMSSet(xf86OutputPtr output, int mode)
45 {
46         ScrnInfoPtr pScrn = output->scrn;
47         NVPtr pNv = NVPTR(pScrn);
48         CARD32 tmp;
49
50         while((NVRead(pNv, NV50_SOR0_DPMS_CTRL + NV50OrOffset(output) * 0x800) & NV50_SOR_DPMS_CTRL_PENDING));
51
52         tmp = NVRead(pNv, NV50_SOR0_DPMS_CTRL + NV50OrOffset(output) * 0x800);
53         tmp |= NV50_SOR_DPMS_CTRL_PENDING;
54
55         if(mode == DPMSModeOn)
56                 tmp |= NV50_SOR_DPMS_CTRL_MODE_ON;
57         else
58                 tmp &= ~NV50_SOR_DPMS_CTRL_MODE_ON;
59
60         NVWrite(pNv, NV50_SOR0_DPMS_CTRL + NV50OrOffset(output) * 0x800, tmp);
61         while((NVRead(pNv, 0x0061c030 + NV50OrOffset(output) * 0x800) & 0x10000000));
62 }
63
64 static int
65 NV50TMDSModeValid(xf86OutputPtr output, DisplayModePtr mode)
66 {
67         // Disable dual-link modes until I can find a way to make them work
68         // reliably.
69         if (mode->Clock > 165000)
70                 return MODE_CLOCK_HIGH;
71
72         return NV50OutputModeValid(output, mode);
73 }
74
75 static int
76 NV50LVDSModeValid(xf86OutputPtr output, DisplayModePtr mode)
77 {
78         NVOutputPrivatePtr nv_output = output->driver_private;
79         DisplayModePtr native = nv_output->native_mode;
80
81         // Ignore modes larger than the native res.
82         if (mode->HDisplay > native->HDisplay || mode->VDisplay > native->VDisplay)
83                 return MODE_PANEL;
84
85         return NV50OutputModeValid(output, mode);
86 }
87
88 static void
89 NV50SorModeSet(xf86OutputPtr output, DisplayModePtr mode,
90                 DisplayModePtr adjusted_mode)
91 {
92         ScrnInfoPtr pScrn = output->scrn;
93         NVOutputPrivatePtr nv_output = output->driver_private;
94         const int sorOff = 0x40 * NV50OrOffset(output);
95         uint32_t mode_ctl = NV50_SOR_MODE_CTRL_OFF;
96
97         if (!adjusted_mode) {
98                 /* Disconnect the SOR */
99                 NV50DisplayCommand(pScrn, NV50_SOR0_MODE_CTRL + sorOff, mode_ctl);
100                 return;
101         }
102
103         if (nv_output->type == OUTPUT_LVDS) {
104                 mode_ctl |= NV50_SOR_MODE_CTRL_LVDS;
105         } else {
106                 mode_ctl |= NV50_SOR_MODE_CTRL_TMDS;
107                 if (adjusted_mode->Clock > 165000)
108                         mode_ctl |= NV50_SOR_MODE_CTRL_TMDS_DUAL_LINK;
109         }
110
111         if (output->crtc) {
112                 NVCrtcPrivatePtr nv_crtc = output->crtc->driver_private;
113                 if (nv_crtc->head == 1)
114                         mode_ctl |= NV50_SOR_MODE_CTRL_CRTC1;
115                 else
116                         mode_ctl |= NV50_SOR_MODE_CTRL_CRTC0;
117         } else {
118                 return;
119         }
120
121         if (adjusted_mode->Flags & V_NHSYNC)
122                 mode_ctl |= NV50_SOR_MODE_CTRL_NHSYNC;
123
124         if (adjusted_mode->Flags & V_NVSYNC)
125                 mode_ctl |= NV50_SOR_MODE_CTRL_NVSYNC;
126
127         // This wouldn't be necessary, but the server is stupid and calls
128         // NV50SorDPMSSet after the output is disconnected, even though the hardware
129         // turns it off automatically.
130         NV50SorDPMSSet(output, DPMSModeOn);
131
132         NV50DisplayCommand(pScrn, NV50_SOR0_MODE_CTRL + sorOff, mode_ctl);
133
134         NV50CrtcSetScale(output->crtc, mode, adjusted_mode, nv_output->scaling_mode);
135 }
136
137 static xf86OutputStatus
138 NV50SorDetect(xf86OutputPtr output)
139 {
140         NVOutputPrivatePtr nv_output = output->driver_private;
141         xf86MonPtr ddc_mon;
142
143         if (nv_output->pDDCBus == NULL)
144                 return XF86OutputStatusDisconnected;
145
146         ddc_mon = NV50OutputGetEDID(output, nv_output->pDDCBus);
147         if (!ddc_mon)
148                 return XF86OutputStatusDisconnected;
149
150         if (!ddc_mon->features.input_type) /* Analog? */
151                 return XF86OutputStatusDisconnected;
152
153         if (ddc_mon)
154                 xf86OutputSetEDID(output, ddc_mon);
155
156         return XF86OutputStatusConnected;
157 }
158
159 static xf86OutputStatus
160 NV50SorLVDSDetect(xf86OutputPtr output)
161 {
162         /* Assume LVDS is always connected */
163         return XF86OutputStatusConnected;
164 }
165
166 static void
167 NV50SorDestroy(xf86OutputPtr output)
168 {
169         NVOutputPrivatePtr nv_output = output->driver_private;
170
171         NV50OutputDestroy(output);
172
173         xf86DeleteMode(&nv_output->native_mode, nv_output->native_mode);
174
175         xfree(output->driver_private);
176         output->driver_private = NULL;
177 }
178
179 static Bool
180 NV50SorModeFixup(xf86OutputPtr output, DisplayModePtr mode,
181                  DisplayModePtr adjusted_mode)
182 {
183         NVOutputPrivatePtr nv_output = output->driver_private;
184
185         if (nv_output->native_mode && nv_output->scaling_mode != SCALE_PANEL) {
186                 adjusted_mode->HDisplay = nv_output->native_mode->HDisplay;
187                 adjusted_mode->HSkew = nv_output->native_mode->HSkew;
188                 adjusted_mode->HSyncStart = nv_output->native_mode->HSyncStart;
189                 adjusted_mode->HSyncEnd = nv_output->native_mode->HSyncEnd;
190                 adjusted_mode->HTotal = nv_output->native_mode->HTotal;
191                 adjusted_mode->VDisplay = nv_output->native_mode->VDisplay;
192                 adjusted_mode->VScan = nv_output->native_mode->VScan;
193                 adjusted_mode->VSyncStart = nv_output->native_mode->VSyncStart;
194                 adjusted_mode->VSyncEnd = nv_output->native_mode->VSyncEnd;
195                 adjusted_mode->VTotal = nv_output->native_mode->VTotal;
196                 adjusted_mode->Clock = nv_output->native_mode->Clock;
197                 adjusted_mode->Flags = nv_output->native_mode->Flags;
198
199                 /* No INTERLACE_HALVE_V, because we manually correct. */
200                 xf86SetModeCrtc(adjusted_mode, 0);
201         }
202         return TRUE;
203 }
204
205 static Bool
206 NV50SorTMDSModeFixup(xf86OutputPtr output, DisplayModePtr mode,
207                         DisplayModePtr adjusted_mode)
208 {
209         int scrnIndex = output->scrn->scrnIndex;
210         NVOutputPrivatePtr nv_output = output->driver_private;
211         DisplayModePtr modes = output->probed_modes;
212
213         xf86DeleteMode(&nv_output->native_mode, nv_output->native_mode);
214
215         if (modes) {
216                 // Find the preferred mode and use that as the "native" mode.
217                 // If no preferred mode is available, use the first one.
218                 DisplayModePtr mode;
219
220                 // Find the preferred mode.
221                 for(mode = modes; mode; mode = mode->next) {
222                         if(mode->type & M_T_PREFERRED) {
223                                 xf86DrvMsgVerb(scrnIndex, X_INFO, 5,
224                                                 "%s: preferred mode is %s\n",
225                                                 output->name, mode->name);
226                                 break;
227                         }
228                 }
229
230                 // XXX: May not want to allow scaling if no preferred mode is found.
231                 if(!mode) {
232                         mode = modes;
233                         xf86DrvMsgVerb(scrnIndex, X_INFO, 5,
234                                 "%s: no preferred mode found, using %s\n",
235                                 output->name, mode->name);
236                 }
237
238                 nv_output->native_mode = xf86DuplicateMode(mode);
239         }
240
241         return NV50SorModeFixup(output, mode, adjusted_mode);
242 }
243
244 static DisplayModePtr
245 NV50SorGetLVDSModes(xf86OutputPtr output)
246 {
247         NVOutputPrivatePtr nv_output = output->driver_private;
248         return xf86DuplicateMode(nv_output->native_mode);
249 }
250
251 static const xf86OutputFuncsRec NV50SorTMDSOutputFuncs = {
252         .dpms = NV50SorDPMSSet,
253         .save = NULL,
254         .restore = NULL,
255         .mode_valid = NV50TMDSModeValid,
256         .mode_fixup = NV50SorTMDSModeFixup,
257         .prepare = NV50OutputPrepare,
258         .commit = NV50OutputCommit,
259         .mode_set = NV50SorModeSet,
260         .detect = NV50SorDetect,
261         .get_modes = NV50OutputGetDDCModes,
262         .create_resources = nv_digital_output_create_resources,
263         .set_property = nv_digital_output_set_property,
264         .destroy = NV50SorDestroy,
265 };
266
267 static const xf86OutputFuncsRec NV50SorLVDSOutputFuncs = {
268         .dpms = NV50SorDPMSSet,
269         .save = NULL,
270         .restore = NULL,
271         .mode_valid = NV50LVDSModeValid,
272         .mode_fixup = NV50SorModeFixup,
273         .prepare = NV50OutputPrepare,
274         .commit = NV50OutputCommit,
275         .mode_set = NV50SorModeSet,
276         .detect = NV50SorLVDSDetect,
277         .get_modes = NV50SorGetLVDSModes,
278         .create_resources = nv_digital_output_create_resources,
279         .set_property = nv_digital_output_set_property,
280         .destroy = NV50SorDestroy,
281 };
282
283 const xf86OutputFuncsRec * nv50_get_tmds_output_funcs()
284 {
285         return &NV50SorTMDSOutputFuncs;
286 }
287
288 const xf86OutputFuncsRec * nv50_get_lvds_output_funcs()
289 {
290         return &NV50SorLVDSOutputFuncs;
291 }
292
293 static DisplayModePtr
294 ReadLVDSNativeMode(ScrnInfoPtr pScrn, const int off)
295 {
296         NVPtr pNv = NVPTR(pScrn);
297         DisplayModePtr mode = xnfcalloc(1, sizeof(DisplayModeRec));
298         const CARD32 size = NVRead(pNv, 0x00610b4c + off);
299         const int width = size & 0x3fff;
300         const int height = (size >> 16) & 0x3fff;
301
302         mode->HDisplay = width;
303         mode->VDisplay = height;
304         mode->Clock = NVRead(pNv, 0x00610ad4 + off) & 0x3fffff;
305
306         /* We should investigate what else is found in these register ranges. */
307         uint32_t unk1 = NVRead(pNv, 0x00610afc + off);
308         uint32_t unk2 = NVRead(pNv, 0x00610b04 + off);
309         uint32_t unk3 = NVRead(pNv, 0x00610ae8 + off);
310         /*uint32_t unk4 = NVRead(pNv, 0x00610af4 + off);*/
311
312         /* Recontruct our mode, so it can be handled normally. */
313         mode->HTotal = (unk1 & 0xFFFF);
314         mode->VTotal = (unk1 >> 16);
315
316         /* Assuming no interlacing. */
317         mode->HSyncStart = mode->HTotal - (unk3 & 0xFFFF) - 1;
318         mode->VSyncStart = mode->VTotal - (unk3 >> 16) - 1;
319
320         mode->HSyncEnd = mode->HSyncStart + (unk2 & 0xFFFF) + 1;
321         mode->VSyncEnd = mode->VSyncStart + (unk2 >> 16) + 1;
322
323         mode->next = mode->prev = NULL;
324         mode->status = MODE_OK;
325         mode->type = M_T_DRIVER | M_T_PREFERRED;
326
327         xf86SetModeDefaultName(mode);
328
329         return mode;
330 }
331
332 DisplayModePtr
333 GetLVDSNativeMode(ScrnInfoPtr pScrn)
334 {
335         NVPtr pNv = NVPTR(pScrn);
336         CARD32 val = NVRead(pNv, 0x00610050);
337
338         if ((val & 0x3) == 0x2) {
339                 return ReadLVDSNativeMode(pScrn, 0);
340         } else if ((val & 0x300) == 0x200) {
341                 return ReadLVDSNativeMode(pScrn, 0x540);
342         }
343
344         return NULL;
345 }