nv50: use bios provided load detect value
[nouveau] / src / nv50_dac.c
1 /*
2  * Copyright 2007 NVIDIA, Corporation
3  * Copyright 2008 Maarten Maathuis
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
20  * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  */
23
24 #include "nouveau_modeset.h"
25 #include "nouveau_crtc.h"
26 #include "nouveau_output.h"
27 #include "nouveau_connector.h"
28
29 static int
30 NV50DacModeValid(nouveauOutputPtr output, DisplayModePtr mode)
31 {
32         if (mode->Clock > 400000)
33                 return MODE_CLOCK_HIGH;
34
35         if (mode->Clock < 25000)
36                 return MODE_CLOCK_LOW;
37
38         return MODE_OK;
39 }
40
41 static void
42 NV50DacModeSet(nouveauOutputPtr output, DisplayModePtr mode)
43 {
44         ScrnInfoPtr pScrn = output->scrn;
45         xf86DrvMsg(pScrn->scrnIndex, X_INFO, "NV50DacModeSet is called.\n");
46
47         const int dacOff = 0x80 * NV50OrOffset(output);
48         uint32_t mode_ctl = NV50_DAC_MODE_CTRL_OFF;
49         uint32_t mode_ctl2 = 0;
50
51         if (!mode) {
52                 xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disconnecting DAC.\n");
53                 NV50DisplayCommand(pScrn, NV50_DAC0_MODE_CTRL + dacOff, mode_ctl);
54                 return;
55         }
56
57         /* Anyone know a more appropriate name? */
58         DisplayModePtr desired_mode = output->crtc->use_native_mode ? output->crtc->native_mode : mode;
59
60         if (output->crtc) {
61                 if (output->crtc->index == 1)
62                         mode_ctl |= NV50_DAC_MODE_CTRL_CRTC1;
63                 else
64                         mode_ctl |= NV50_DAC_MODE_CTRL_CRTC0;
65         } else {
66                 xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "Warning, output has no crtc.\n");
67                 return;
68         }
69
70         if (output->type == OUTPUT_ANALOG) {
71                 /* What is this? */
72                 mode_ctl |= 0x40;
73         } else if (output->type == OUTPUT_TV) {
74                 mode_ctl |= 0x100;
75         }
76
77         if (desired_mode->Flags & V_NHSYNC)
78                 mode_ctl2 |= NV50_DAC_MODE_CTRL2_NHSYNC;
79
80         if (desired_mode->Flags & V_NVSYNC)
81                 mode_ctl2 |= NV50_DAC_MODE_CTRL2_NVSYNC;
82
83         // This wouldn't be necessary, but the server is stupid and calls
84         // nv50_dac_dpms after the output is disconnected, even though the hardware
85         // turns it off automatically.
86         output->SetPowerMode(output, DPMSModeOn);
87
88         NV50DisplayCommand(pScrn, NV50_DAC0_MODE_CTRL + dacOff, mode_ctl);
89
90         NV50DisplayCommand(pScrn, NV50_DAC0_MODE_CTRL2 + dacOff, mode_ctl2);
91
92         output->crtc->SetScaleMode(output->crtc, output->scale_mode);
93 }
94
95 static void
96 NV50DacSetClockMode(nouveauOutputPtr output, int clock)
97 {
98         ScrnInfoPtr pScrn = output->scrn;
99         xf86DrvMsg(pScrn->scrnIndex, X_INFO, "NV50DacSetClockMode is called.\n");
100
101         NVPtr pNv = NVPTR(pScrn);
102         NVWrite(pNv, NV50_DAC0_CLK_CTRL2 + NV50OrOffset(output) * 0x800, 0);
103 }
104
105 static int
106 NV50DacSense(nouveauOutputPtr output)
107 {
108         switch (output->type) {
109                 case OUTPUT_ANALOG:
110                 case OUTPUT_TV:
111                         return output->type;
112                 default:
113                         return OUTPUT_NONE;
114         }
115 }
116
117 static Bool
118 NV50DacDetect (nouveauOutputPtr output)
119 {
120         ScrnInfoPtr pScrn = output->scrn;
121         NVPtr pNv = NVPTR(pScrn);
122         const int scrnIndex = pScrn->scrnIndex;
123         int sigstate;
124         uint32_t load, dactestval, tmp;
125
126         NVWrite(pNv, NV50_DAC0_CLK_CTRL1 + NV50OrOffset(output) * 0x800, 0x00000001);
127         tmp = NVRead(pNv, NV50_DAC0_DPMS_CTRL + NV50OrOffset(output) * 0x800);
128
129         NVWrite(pNv, NV50_DAC0_DPMS_CTRL + NV50OrOffset(output) * 0x800, NV50_DAC_DPMS_CTRL_DEFAULT_STATE | NV50_DAC_DPMS_CTRL_PENDING);
130         while (NVRead(pNv, NV50_DAC0_DPMS_CTRL + NV50OrOffset(output) * 0x800) & NV50_DAC_DPMS_CTRL_PENDING);
131
132         dactestval = 340;
133         if (pNv->VBIOS.dactestval) {
134                 dactestval = pNv->VBIOS.dactestval;
135                 xf86DrvMsg(scrnIndex, X_INFO, "Using bios provided load value of %d\n", dactestval);
136         } else {
137                 xf86DrvMsg(scrnIndex, X_INFO, "Using default load value of %d\n", dactestval);
138         }
139
140         NVWrite(pNv, NV50_DAC0_LOAD_CTRL + NV50OrOffset(output) * 0x800, dactestval | NV50_DAC_LOAD_CTRL_ACTIVE);
141         /* Why is this needed, load detect is almost instantanious and seemingly reliable for me. */
142         sigstate = xf86BlockSIGIO();
143         usleep(45000);
144         xf86UnblockSIGIO(sigstate);
145         load = NVRead(pNv, NV50_DAC0_LOAD_CTRL + NV50OrOffset(output) * 0x800);
146         NVWrite(pNv, NV50_DAC0_LOAD_CTRL + NV50OrOffset(output) * 0x800, 0);
147         NVWrite(pNv, NV50_DAC0_DPMS_CTRL + NV50OrOffset(output) * 0x800, NV50_DAC_DPMS_CTRL_PENDING | tmp);
148
149         // Use this DAC if all three channels show load.
150         if ((load & NV50_DAC_LOAD_CTRL_PRESENT) == NV50_DAC_LOAD_CTRL_PRESENT) {
151                 xf86DrvMsg(scrnIndex, X_PROBED, "Load present on DAC-%i\n", NV50OrOffset(output));
152                 return TRUE;
153         }
154
155         xf86DrvMsg(scrnIndex, X_PROBED, "No Load present on DAC-%i\n", NV50OrOffset(output));
156         return FALSE;
157 }
158
159 static void
160 NV50DacSetPowerMode(nouveauOutputPtr output, int mode)
161 {
162         ScrnInfoPtr pScrn = output->scrn;
163         xf86DrvMsg(pScrn->scrnIndex, X_INFO, "NV50DacSetPowerMode is called with mode %d.\n", mode);
164
165         uint32_t tmp;
166         NVPtr pNv = NVPTR(pScrn);
167
168         /*
169          * DPMSModeOn       everything on
170          * DPMSModeStandby  hsync disabled, vsync enabled
171          * DPMSModeSuspend  hsync enabled, vsync disabled
172          * DPMSModeOff      sync disabled
173          */
174         while(NVRead(pNv, NV50_DAC0_DPMS_CTRL + NV50OrOffset(output) * 0x800) & NV50_DAC_DPMS_CTRL_PENDING);
175
176         tmp = NVRead(pNv, NV50_DAC0_DPMS_CTRL + NV50OrOffset(output) * 0x800);
177         tmp &= ~0x7f;
178         tmp |= NV50_DAC_DPMS_CTRL_PENDING;
179
180         if (mode == DPMSModeStandby || mode == DPMSModeOff)
181                 tmp |= NV50_DAC_DPMS_CTRL_HSYNC_OFF;
182         if (mode == DPMSModeSuspend || mode == DPMSModeOff)
183                 tmp |= NV50_DAC_DPMS_CTRL_VSYNC_OFF;
184         if (mode != DPMSModeOn)
185                 tmp |= NV50_DAC_DPMS_CTRL_BLANKED;
186         if (mode == DPMSModeOff)
187                 tmp |= NV50_DAC_DPMS_CTRL_OFF;
188
189         NVWrite(pNv, NV50_DAC0_DPMS_CTRL + NV50OrOffset(output) * 0x800, tmp);
190 }
191
192 void
193 NV50DacSetFunctionPointers(nouveauOutputPtr output)
194 {
195         output->ModeValid = NV50DacModeValid;
196         output->ModeSet = NV50DacModeSet;
197         output->SetClockMode = NV50DacSetClockMode;
198         output->Sense = NV50DacSense;
199         output->Detect = NV50DacDetect;
200         output->SetPowerMode = NV50DacSetPowerMode;
201 }