aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/nouveau/nv04_dfp.c
diff options
context:
space:
mode:
authorBen Skeggs <bskeggs@redhat.com>2009-12-11 04:24:15 -0500
committerDave Airlie <airlied@redhat.com>2009-12-11 06:29:34 -0500
commit6ee738610f41b59733f63718f0bdbcba7d3a3f12 (patch)
treeeccb9f07671998c50a1bc606a54cd6f82ba43e0a /drivers/gpu/drm/nouveau/nv04_dfp.c
parentd1ede145cea25c5b6d2ebb19b167af14e374bb45 (diff)
drm/nouveau: Add DRM driver for NVIDIA GPUs
This adds a drm/kms staging non-API stable driver for GPUs from NVIDIA. This driver is a KMS-based driver and requires a compatible nouveau userspace libdrm and nouveau X.org driver. This driver requires firmware files not available in this kernel tree, interested parties can find them via the nouveau project git archive. This driver is reverse engineered, and is in no way supported by nVidia. Support for nearly the complete range of nvidia hw from nv04->g80 (nv50) is available, and the kms driver should support driving nearly all output types (displayport is under development still) along with supporting suspend/resume. This work is all from the upstream nouveau project found at nouveau.freedesktop.org. The original authors list from nouveau git tree is: Anssi Hannula <anssi.hannula@iki.fi> Ben Skeggs <bskeggs@redhat.com> Francisco Jerez <currojerez@riseup.net> Maarten Maathuis <madman2003@gmail.com> Marcin Koƛcielnicki <koriakin@0x04.net> Matthew Garrett <mjg@redhat.com> Matt Parnell <mparnell@gmail.com> Patrice Mandin <patmandin@gmail.com> Pekka Paalanen <pq@iki.fi> Xavier Chantry <shiningxc@gmail.com> along with project founder Stephane Marchesin <marchesin@icps.u-strasbg.fr> Signed-off-by: Ben Skeggs <bskeggs@redhat.com> Signed-off-by: Dave Airlie <airlied@redhat.com>
Diffstat (limited to 'drivers/gpu/drm/nouveau/nv04_dfp.c')
-rw-r--r--drivers/gpu/drm/nouveau/nv04_dfp.c621
1 files changed, 621 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/nv04_dfp.c b/drivers/gpu/drm/nouveau/nv04_dfp.c
new file mode 100644
index 000000000000..e5b33339d595
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nv04_dfp.c
@@ -0,0 +1,621 @@
1/*
2 * Copyright 2003 NVIDIA, Corporation
3 * Copyright 2006 Dave Airlie
4 * Copyright 2007 Maarten Maathuis
5 * Copyright 2007-2009 Stuart Bennett
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a
8 * copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation
10 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 * and/or sell copies of the Software, and to permit persons to whom the
12 * Software is furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice (including the next
15 * paragraph) shall be included in all copies or substantial portions of the
16 * Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
25 */
26
27#include "drmP.h"
28#include "drm_crtc_helper.h"
29
30#include "nouveau_drv.h"
31#include "nouveau_encoder.h"
32#include "nouveau_connector.h"
33#include "nouveau_crtc.h"
34#include "nouveau_hw.h"
35#include "nvreg.h"
36
37#define FP_TG_CONTROL_ON (NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS | \
38 NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS | \
39 NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS)
40#define FP_TG_CONTROL_OFF (NV_PRAMDAC_FP_TG_CONTROL_DISPEN_DISABLE | \
41 NV_PRAMDAC_FP_TG_CONTROL_HSYNC_DISABLE | \
42 NV_PRAMDAC_FP_TG_CONTROL_VSYNC_DISABLE)
43
44static inline bool is_fpc_off(uint32_t fpc)
45{
46 return ((fpc & (FP_TG_CONTROL_ON | FP_TG_CONTROL_OFF)) ==
47 FP_TG_CONTROL_OFF);
48}
49
50int nv04_dfp_get_bound_head(struct drm_device *dev, struct dcb_entry *dcbent)
51{
52 /* special case of nv_read_tmds to find crtc associated with an output.
53 * this does not give a correct answer for off-chip dvi, but there's no
54 * use for such an answer anyway
55 */
56 int ramdac = (dcbent->or & OUTPUT_C) >> 2;
57
58 NVWriteRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_CONTROL,
59 NV_PRAMDAC_FP_TMDS_CONTROL_WRITE_DISABLE | 0x4);
60 return ((NVReadRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_DATA) & 0x8) >> 3) ^ ramdac;
61}
62
63void nv04_dfp_bind_head(struct drm_device *dev, struct dcb_entry *dcbent,
64 int head, bool dl)
65{
66 /* The BIOS scripts don't do this for us, sadly
67 * Luckily we do know the values ;-)
68 *
69 * head < 0 indicates we wish to force a setting with the overrideval
70 * (for VT restore etc.)
71 */
72
73 int ramdac = (dcbent->or & OUTPUT_C) >> 2;
74 uint8_t tmds04 = 0x80;
75
76 if (head != ramdac)
77 tmds04 = 0x88;
78
79 if (dcbent->type == OUTPUT_LVDS)
80 tmds04 |= 0x01;
81
82 nv_write_tmds(dev, dcbent->or, 0, 0x04, tmds04);
83
84 if (dl) /* dual link */
85 nv_write_tmds(dev, dcbent->or, 1, 0x04, tmds04 ^ 0x08);
86}
87
88void nv04_dfp_disable(struct drm_device *dev, int head)
89{
90 struct drm_nouveau_private *dev_priv = dev->dev_private;
91 struct nv04_crtc_reg *crtcstate = dev_priv->mode_reg.crtc_reg;
92
93 if (NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL) &
94 FP_TG_CONTROL_ON) {
95 /* digital remnants must be cleaned before new crtc
96 * values programmed. delay is time for the vga stuff
97 * to realise it's in control again
98 */
99 NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL,
100 FP_TG_CONTROL_OFF);
101 msleep(50);
102 }
103 /* don't inadvertently turn it on when state written later */
104 crtcstate[head].fp_control = FP_TG_CONTROL_OFF;
105}
106
107void nv04_dfp_update_fp_control(struct drm_encoder *encoder, int mode)
108{
109 struct drm_device *dev = encoder->dev;
110 struct drm_nouveau_private *dev_priv = dev->dev_private;
111 struct drm_crtc *crtc;
112 struct nouveau_crtc *nv_crtc;
113 uint32_t *fpc;
114
115 if (mode == DRM_MODE_DPMS_ON) {
116 nv_crtc = nouveau_crtc(encoder->crtc);
117 fpc = &dev_priv->mode_reg.crtc_reg[nv_crtc->index].fp_control;
118
119 if (is_fpc_off(*fpc)) {
120 /* using saved value is ok, as (is_digital && dpms_on &&
121 * fp_control==OFF) is (at present) *only* true when
122 * fpc's most recent change was by below "off" code
123 */
124 *fpc = nv_crtc->dpms_saved_fp_control;
125 }
126
127 nv_crtc->fp_users |= 1 << nouveau_encoder(encoder)->dcb->index;
128 NVWriteRAMDAC(dev, nv_crtc->index, NV_PRAMDAC_FP_TG_CONTROL, *fpc);
129 } else {
130 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
131 nv_crtc = nouveau_crtc(crtc);
132 fpc = &dev_priv->mode_reg.crtc_reg[nv_crtc->index].fp_control;
133
134 nv_crtc->fp_users &= ~(1 << nouveau_encoder(encoder)->dcb->index);
135 if (!is_fpc_off(*fpc) && !nv_crtc->fp_users) {
136 nv_crtc->dpms_saved_fp_control = *fpc;
137 /* cut the FP output */
138 *fpc &= ~FP_TG_CONTROL_ON;
139 *fpc |= FP_TG_CONTROL_OFF;
140 NVWriteRAMDAC(dev, nv_crtc->index,
141 NV_PRAMDAC_FP_TG_CONTROL, *fpc);
142 }
143 }
144 }
145}
146
147static bool nv04_dfp_mode_fixup(struct drm_encoder *encoder,
148 struct drm_display_mode *mode,
149 struct drm_display_mode *adjusted_mode)
150{
151 struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
152 struct nouveau_connector *nv_connector = nouveau_encoder_connector_get(nv_encoder);
153
154 /* For internal panels and gpu scaling on DVI we need the native mode */
155 if (nv_connector->scaling_mode != DRM_MODE_SCALE_NONE) {
156 if (!nv_connector->native_mode)
157 return false;
158 nv_encoder->mode = *nv_connector->native_mode;
159 adjusted_mode->clock = nv_connector->native_mode->clock;
160 } else {
161 nv_encoder->mode = *adjusted_mode;
162 }
163
164 return true;
165}
166
167static void nv04_dfp_prepare_sel_clk(struct drm_device *dev,
168 struct nouveau_encoder *nv_encoder, int head)
169{
170 struct drm_nouveau_private *dev_priv = dev->dev_private;
171 struct nv04_mode_state *state = &dev_priv->mode_reg;
172 uint32_t bits1618 = nv_encoder->dcb->or & OUTPUT_A ? 0x10000 : 0x40000;
173
174 if (nv_encoder->dcb->location != DCB_LOC_ON_CHIP)
175 return;
176
177 /* SEL_CLK is only used on the primary ramdac
178 * It toggles spread spectrum PLL output and sets the bindings of PLLs
179 * to heads on digital outputs
180 */
181 if (head)
182 state->sel_clk |= bits1618;
183 else
184 state->sel_clk &= ~bits1618;
185
186 /* nv30:
187 * bit 0 NVClk spread spectrum on/off
188 * bit 2 MemClk spread spectrum on/off
189 * bit 4 PixClk1 spread spectrum on/off toggle
190 * bit 6 PixClk2 spread spectrum on/off toggle
191 *
192 * nv40 (observations from bios behaviour and mmio traces):
193 * bits 4&6 as for nv30
194 * bits 5&7 head dependent as for bits 4&6, but do not appear with 4&6;
195 * maybe a different spread mode
196 * bits 8&10 seen on dual-link dvi outputs, purpose unknown (set by POST scripts)
197 * The logic behind turning spread spectrum on/off in the first place,
198 * and which bit-pair to use, is unclear on nv40 (for earlier cards, the fp table
199 * entry has the necessary info)
200 */
201 if (nv_encoder->dcb->type == OUTPUT_LVDS && dev_priv->saved_reg.sel_clk & 0xf0) {
202 int shift = (dev_priv->saved_reg.sel_clk & 0x50) ? 0 : 1;
203
204 state->sel_clk &= ~0xf0;
205 state->sel_clk |= (head ? 0x40 : 0x10) << shift;
206 }
207}
208
209static void nv04_dfp_prepare(struct drm_encoder *encoder)
210{
211 struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
212 struct drm_encoder_helper_funcs *helper = encoder->helper_private;
213 struct drm_device *dev = encoder->dev;
214 struct drm_nouveau_private *dev_priv = dev->dev_private;
215 int head = nouveau_crtc(encoder->crtc)->index;
216 struct nv04_crtc_reg *crtcstate = dev_priv->mode_reg.crtc_reg;
217 uint8_t *cr_lcd = &crtcstate[head].CRTC[NV_CIO_CRE_LCD__INDEX];
218 uint8_t *cr_lcd_oth = &crtcstate[head ^ 1].CRTC[NV_CIO_CRE_LCD__INDEX];
219
220 helper->dpms(encoder, DRM_MODE_DPMS_OFF);
221
222 nv04_dfp_prepare_sel_clk(dev, nv_encoder, head);
223
224 /* Some NV4x have unknown values (0x3f, 0x50, 0x54, 0x6b, 0x79, 0x7f)
225 * at LCD__INDEX which we don't alter
226 */
227 if (!(*cr_lcd & 0x44)) {
228 *cr_lcd = 0x3;
229
230 if (nv_two_heads(dev)) {
231 if (nv_encoder->dcb->location == DCB_LOC_ON_CHIP)
232 *cr_lcd |= head ? 0x0 : 0x8;
233 else {
234 *cr_lcd |= (nv_encoder->dcb->or << 4) & 0x30;
235 if (nv_encoder->dcb->type == OUTPUT_LVDS)
236 *cr_lcd |= 0x30;
237 if ((*cr_lcd & 0x30) == (*cr_lcd_oth & 0x30)) {
238 /* avoid being connected to both crtcs */
239 *cr_lcd_oth &= ~0x30;
240 NVWriteVgaCrtc(dev, head ^ 1,
241 NV_CIO_CRE_LCD__INDEX,
242 *cr_lcd_oth);
243 }
244 }
245 }
246 }
247}
248
249
250static void nv04_dfp_mode_set(struct drm_encoder *encoder,
251 struct drm_display_mode *mode,
252 struct drm_display_mode *adjusted_mode)
253{
254 struct drm_device *dev = encoder->dev;
255 struct drm_nouveau_private *dev_priv = dev->dev_private;
256 struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
257 struct nv04_crtc_reg *regp = &dev_priv->mode_reg.crtc_reg[nv_crtc->index];
258 struct nv04_crtc_reg *savep = &dev_priv->saved_reg.crtc_reg[nv_crtc->index];
259 struct nouveau_connector *nv_connector = nouveau_crtc_connector_get(nv_crtc);
260 struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
261 struct drm_display_mode *output_mode = &nv_encoder->mode;
262 uint32_t mode_ratio, panel_ratio;
263
264 NV_DEBUG(dev, "Output mode on CRTC %d:\n", nv_crtc->index);
265 drm_mode_debug_printmodeline(output_mode);
266
267 /* Initialize the FP registers in this CRTC. */
268 regp->fp_horiz_regs[FP_DISPLAY_END] = output_mode->hdisplay - 1;
269 regp->fp_horiz_regs[FP_TOTAL] = output_mode->htotal - 1;
270 if (!nv_gf4_disp_arch(dev) ||
271 (output_mode->hsync_start - output_mode->hdisplay) >=
272 dev_priv->vbios->digital_min_front_porch)
273 regp->fp_horiz_regs[FP_CRTC] = output_mode->hdisplay;
274 else
275 regp->fp_horiz_regs[FP_CRTC] = output_mode->hsync_start - dev_priv->vbios->digital_min_front_porch - 1;
276 regp->fp_horiz_regs[FP_SYNC_START] = output_mode->hsync_start - 1;
277 regp->fp_horiz_regs[FP_SYNC_END] = output_mode->hsync_end - 1;
278 regp->fp_horiz_regs[FP_VALID_START] = output_mode->hskew;
279 regp->fp_horiz_regs[FP_VALID_END] = output_mode->hdisplay - 1;
280
281 regp->fp_vert_regs[FP_DISPLAY_END] = output_mode->vdisplay - 1;
282 regp->fp_vert_regs[FP_TOTAL] = output_mode->vtotal - 1;
283 regp->fp_vert_regs[FP_CRTC] = output_mode->vtotal - 5 - 1;
284 regp->fp_vert_regs[FP_SYNC_START] = output_mode->vsync_start - 1;
285 regp->fp_vert_regs[FP_SYNC_END] = output_mode->vsync_end - 1;
286 regp->fp_vert_regs[FP_VALID_START] = 0;
287 regp->fp_vert_regs[FP_VALID_END] = output_mode->vdisplay - 1;
288
289 /* bit26: a bit seen on some g7x, no as yet discernable purpose */
290 regp->fp_control = NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS |
291 (savep->fp_control & (1 << 26 | NV_PRAMDAC_FP_TG_CONTROL_READ_PROG));
292 /* Deal with vsync/hsync polarity */
293 /* LVDS screens do set this, but modes with +ve syncs are very rare */
294 if (output_mode->flags & DRM_MODE_FLAG_PVSYNC)
295 regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS;
296 if (output_mode->flags & DRM_MODE_FLAG_PHSYNC)
297 regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS;
298 /* panel scaling first, as native would get set otherwise */
299 if (nv_connector->scaling_mode == DRM_MODE_SCALE_NONE ||
300 nv_connector->scaling_mode == DRM_MODE_SCALE_CENTER) /* panel handles it */
301 regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_MODE_CENTER;
302 else if (adjusted_mode->hdisplay == output_mode->hdisplay &&
303 adjusted_mode->vdisplay == output_mode->vdisplay) /* native mode */
304 regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_MODE_NATIVE;
305 else /* gpu needs to scale */
306 regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_MODE_SCALE;
307 if (nvReadEXTDEV(dev, NV_PEXTDEV_BOOT_0) & NV_PEXTDEV_BOOT_0_STRAP_FP_IFACE_12BIT)
308 regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_WIDTH_12;
309 if (nv_encoder->dcb->location != DCB_LOC_ON_CHIP &&
310 output_mode->clock > 165000)
311 regp->fp_control |= (2 << 24);
312 if (nv_encoder->dcb->type == OUTPUT_LVDS) {
313 bool duallink, dummy;
314
315 nouveau_bios_parse_lvds_table(dev, nv_connector->native_mode->
316 clock, &duallink, &dummy);
317 if (duallink)
318 regp->fp_control |= (8 << 28);
319 } else
320 if (output_mode->clock > 165000)
321 regp->fp_control |= (8 << 28);
322
323 regp->fp_debug_0 = NV_PRAMDAC_FP_DEBUG_0_YWEIGHT_ROUND |
324 NV_PRAMDAC_FP_DEBUG_0_XWEIGHT_ROUND |
325 NV_PRAMDAC_FP_DEBUG_0_YINTERP_BILINEAR |
326 NV_PRAMDAC_FP_DEBUG_0_XINTERP_BILINEAR |
327 NV_RAMDAC_FP_DEBUG_0_TMDS_ENABLED |
328 NV_PRAMDAC_FP_DEBUG_0_YSCALE_ENABLE |
329 NV_PRAMDAC_FP_DEBUG_0_XSCALE_ENABLE;
330
331 /* We want automatic scaling */
332 regp->fp_debug_1 = 0;
333 /* This can override HTOTAL and VTOTAL */
334 regp->fp_debug_2 = 0;
335
336 /* Use 20.12 fixed point format to avoid floats */
337 mode_ratio = (1 << 12) * adjusted_mode->hdisplay / adjusted_mode->vdisplay;
338 panel_ratio = (1 << 12) * output_mode->hdisplay / output_mode->vdisplay;
339 /* if ratios are equal, SCALE_ASPECT will automatically (and correctly)
340 * get treated the same as SCALE_FULLSCREEN */
341 if (nv_connector->scaling_mode == DRM_MODE_SCALE_ASPECT &&
342 mode_ratio != panel_ratio) {
343 uint32_t diff, scale;
344 bool divide_by_2 = nv_gf4_disp_arch(dev);
345
346 if (mode_ratio < panel_ratio) {
347 /* vertical needs to expand to glass size (automatic)
348 * horizontal needs to be scaled at vertical scale factor
349 * to maintain aspect */
350
351 scale = (1 << 12) * adjusted_mode->vdisplay / output_mode->vdisplay;
352 regp->fp_debug_1 = NV_PRAMDAC_FP_DEBUG_1_XSCALE_TESTMODE_ENABLE |
353 XLATE(scale, divide_by_2, NV_PRAMDAC_FP_DEBUG_1_XSCALE_VALUE);
354
355 /* restrict area of screen used, horizontally */
356 diff = output_mode->hdisplay -
357 output_mode->vdisplay * mode_ratio / (1 << 12);
358 regp->fp_horiz_regs[FP_VALID_START] += diff / 2;
359 regp->fp_horiz_regs[FP_VALID_END] -= diff / 2;
360 }
361
362 if (mode_ratio > panel_ratio) {
363 /* horizontal needs to expand to glass size (automatic)
364 * vertical needs to be scaled at horizontal scale factor
365 * to maintain aspect */
366
367 scale = (1 << 12) * adjusted_mode->hdisplay / output_mode->hdisplay;
368 regp->fp_debug_1 = NV_PRAMDAC_FP_DEBUG_1_YSCALE_TESTMODE_ENABLE |
369 XLATE(scale, divide_by_2, NV_PRAMDAC_FP_DEBUG_1_YSCALE_VALUE);
370
371 /* restrict area of screen used, vertically */
372 diff = output_mode->vdisplay -
373 (1 << 12) * output_mode->hdisplay / mode_ratio;
374 regp->fp_vert_regs[FP_VALID_START] += diff / 2;
375 regp->fp_vert_regs[FP_VALID_END] -= diff / 2;
376 }
377 }
378
379 /* Output property. */
380 if (nv_connector->use_dithering) {
381 if (dev_priv->chipset == 0x11)
382 regp->dither = savep->dither | 0x00010000;
383 else {
384 int i;
385 regp->dither = savep->dither | 0x00000001;
386 for (i = 0; i < 3; i++) {
387 regp->dither_regs[i] = 0xe4e4e4e4;
388 regp->dither_regs[i + 3] = 0x44444444;
389 }
390 }
391 } else {
392 if (dev_priv->chipset != 0x11) {
393 /* reset them */
394 int i;
395 for (i = 0; i < 3; i++) {
396 regp->dither_regs[i] = savep->dither_regs[i];
397 regp->dither_regs[i + 3] = savep->dither_regs[i + 3];
398 }
399 }
400 regp->dither = savep->dither;
401 }
402
403 regp->fp_margin_color = 0;
404}
405
406static void nv04_dfp_commit(struct drm_encoder *encoder)
407{
408 struct drm_device *dev = encoder->dev;
409 struct drm_nouveau_private *dev_priv = dev->dev_private;
410 struct drm_encoder_helper_funcs *helper = encoder->helper_private;
411 struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
412 struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
413 struct dcb_entry *dcbe = nv_encoder->dcb;
414 int head = nouveau_crtc(encoder->crtc)->index;
415
416 NV_TRACE(dev, "%s called for encoder %d\n", __func__, nv_encoder->dcb->index);
417
418 if (dcbe->type == OUTPUT_TMDS)
419 run_tmds_table(dev, dcbe, head, nv_encoder->mode.clock);
420 else if (dcbe->type == OUTPUT_LVDS)
421 call_lvds_script(dev, dcbe, head, LVDS_RESET, nv_encoder->mode.clock);
422
423 /* update fp_control state for any changes made by scripts,
424 * so correct value is written at DPMS on */
425 dev_priv->mode_reg.crtc_reg[head].fp_control =
426 NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL);
427
428 /* This could use refinement for flatpanels, but it should work this way */
429 if (dev_priv->chipset < 0x44)
430 NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0xf0000000);
431 else
432 NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0x00100000);
433
434 helper->dpms(encoder, DRM_MODE_DPMS_ON);
435
436 NV_INFO(dev, "Output %s is running on CRTC %d using output %c\n",
437 drm_get_connector_name(&nouveau_encoder_connector_get(nv_encoder)->base),
438 nv_crtc->index, '@' + ffs(nv_encoder->dcb->or));
439}
440
441static inline bool is_powersaving_dpms(int mode)
442{
443 return (mode != DRM_MODE_DPMS_ON);
444}
445
446static void nv04_lvds_dpms(struct drm_encoder *encoder, int mode)
447{
448 struct drm_device *dev = encoder->dev;
449 struct drm_crtc *crtc = encoder->crtc;
450 struct drm_nouveau_private *dev_priv = dev->dev_private;
451 struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
452 bool was_powersaving = is_powersaving_dpms(nv_encoder->last_dpms);
453
454 if (nv_encoder->last_dpms == mode)
455 return;
456 nv_encoder->last_dpms = mode;
457
458 NV_INFO(dev, "Setting dpms mode %d on lvds encoder (output %d)\n",
459 mode, nv_encoder->dcb->index);
460
461 if (was_powersaving && is_powersaving_dpms(mode))
462 return;
463
464 if (nv_encoder->dcb->lvdsconf.use_power_scripts) {
465 struct nouveau_connector *nv_connector = nouveau_encoder_connector_get(nv_encoder);
466
467 /* when removing an output, crtc may not be set, but PANEL_OFF
468 * must still be run
469 */
470 int head = crtc ? nouveau_crtc(crtc)->index :
471 nv04_dfp_get_bound_head(dev, nv_encoder->dcb);
472
473 if (mode == DRM_MODE_DPMS_ON) {
474 if (!nv_connector->native_mode) {
475 NV_ERROR(dev, "Not turning on LVDS without native mode\n");
476 return;
477 }
478 call_lvds_script(dev, nv_encoder->dcb, head,
479 LVDS_PANEL_ON, nv_connector->native_mode->clock);
480 } else
481 /* pxclk of 0 is fine for PANEL_OFF, and for a
482 * disconnected LVDS encoder there is no native_mode
483 */
484 call_lvds_script(dev, nv_encoder->dcb, head,
485 LVDS_PANEL_OFF, 0);
486 }
487
488 nv04_dfp_update_fp_control(encoder, mode);
489
490 if (mode == DRM_MODE_DPMS_ON)
491 nv04_dfp_prepare_sel_clk(dev, nv_encoder, nouveau_crtc(crtc)->index);
492 else {
493 dev_priv->mode_reg.sel_clk = NVReadRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK);
494 dev_priv->mode_reg.sel_clk &= ~0xf0;
495 }
496 NVWriteRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK, dev_priv->mode_reg.sel_clk);
497}
498
499static void nv04_tmds_dpms(struct drm_encoder *encoder, int mode)
500{
501 struct drm_device *dev = encoder->dev;
502 struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
503
504 if (nv_encoder->last_dpms == mode)
505 return;
506 nv_encoder->last_dpms = mode;
507
508 NV_INFO(dev, "Setting dpms mode %d on tmds encoder (output %d)\n",
509 mode, nv_encoder->dcb->index);
510
511 nv04_dfp_update_fp_control(encoder, mode);
512}
513
514static void nv04_dfp_save(struct drm_encoder *encoder)
515{
516 struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
517 struct drm_device *dev = encoder->dev;
518
519 if (nv_two_heads(dev))
520 nv_encoder->restore.head =
521 nv04_dfp_get_bound_head(dev, nv_encoder->dcb);
522}
523
524static void nv04_dfp_restore(struct drm_encoder *encoder)
525{
526 struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
527 struct drm_device *dev = encoder->dev;
528 struct drm_nouveau_private *dev_priv = dev->dev_private;
529 int head = nv_encoder->restore.head;
530
531 if (nv_encoder->dcb->type == OUTPUT_LVDS) {
532 struct drm_display_mode *native_mode = nouveau_encoder_connector_get(nv_encoder)->native_mode;
533 if (native_mode)
534 call_lvds_script(dev, nv_encoder->dcb, head, LVDS_PANEL_ON,
535 native_mode->clock);
536 else
537 NV_ERROR(dev, "Not restoring LVDS without native mode\n");
538
539 } else if (nv_encoder->dcb->type == OUTPUT_TMDS) {
540 int clock = nouveau_hw_pllvals_to_clk
541 (&dev_priv->saved_reg.crtc_reg[head].pllvals);
542
543 run_tmds_table(dev, nv_encoder->dcb, head, clock);
544 }
545
546 nv_encoder->last_dpms = NV_DPMS_CLEARED;
547}
548
549static void nv04_dfp_destroy(struct drm_encoder *encoder)
550{
551 struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
552
553 NV_DEBUG(encoder->dev, "\n");
554
555 drm_encoder_cleanup(encoder);
556 kfree(nv_encoder);
557}
558
559static const struct drm_encoder_helper_funcs nv04_lvds_helper_funcs = {
560 .dpms = nv04_lvds_dpms,
561 .save = nv04_dfp_save,
562 .restore = nv04_dfp_restore,
563 .mode_fixup = nv04_dfp_mode_fixup,
564 .prepare = nv04_dfp_prepare,
565 .commit = nv04_dfp_commit,
566 .mode_set = nv04_dfp_mode_set,
567 .detect = NULL,
568};
569
570static const struct drm_encoder_helper_funcs nv04_tmds_helper_funcs = {
571 .dpms = nv04_tmds_dpms,
572 .save = nv04_dfp_save,
573 .restore = nv04_dfp_restore,
574 .mode_fixup = nv04_dfp_mode_fixup,
575 .prepare = nv04_dfp_prepare,
576 .commit = nv04_dfp_commit,
577 .mode_set = nv04_dfp_mode_set,
578 .detect = NULL,
579};
580
581static const struct drm_encoder_funcs nv04_dfp_funcs = {
582 .destroy = nv04_dfp_destroy,
583};
584
585int nv04_dfp_create(struct drm_device *dev, struct dcb_entry *entry)
586{
587 const struct drm_encoder_helper_funcs *helper;
588 struct drm_encoder *encoder;
589 struct nouveau_encoder *nv_encoder = NULL;
590 int type;
591
592 switch (entry->type) {
593 case OUTPUT_TMDS:
594 type = DRM_MODE_ENCODER_TMDS;
595 helper = &nv04_tmds_helper_funcs;
596 break;
597 case OUTPUT_LVDS:
598 type = DRM_MODE_ENCODER_LVDS;
599 helper = &nv04_lvds_helper_funcs;
600 break;
601 default:
602 return -EINVAL;
603 }
604
605 nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
606 if (!nv_encoder)
607 return -ENOMEM;
608
609 encoder = to_drm_encoder(nv_encoder);
610
611 nv_encoder->dcb = entry;
612 nv_encoder->or = ffs(entry->or) - 1;
613
614 drm_encoder_init(dev, encoder, &nv04_dfp_funcs, type);
615 drm_encoder_helper_add(encoder, helper);
616
617 encoder->possible_crtcs = entry->heads;
618 encoder->possible_clones = 0;
619
620 return 0;
621}