1/*
2 * Copyright © 2006 Intel 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 "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 (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * 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 OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 * Authors:
24 *    Eric Anholt <eric@anholt.net>
25 *
26 */
27
28#ifdef HAVE_CONFIG_H
29#include "config.h"
30#endif
31
32#include "xf86.h"
33#include "i830.h"
34#include "xf86Modes.h"
35#include "i830_display.h"
36
37static void
38i830_crt_dpms(xf86OutputPtr output, int mode)
39{
40    ScrnInfoPtr	    pScrn = output->scrn;
41    I830Ptr	    pI830 = I830PTR(pScrn);
42    uint32_t	    temp;
43
44    temp = INREG(ADPA);
45    temp &= ~(ADPA_HSYNC_CNTL_DISABLE | ADPA_VSYNC_CNTL_DISABLE);
46    temp &= ~ADPA_DAC_ENABLE;
47
48    switch(mode) {
49    case DPMSModeOn:
50	temp |= ADPA_DAC_ENABLE;
51	break;
52    case DPMSModeStandby:
53	temp |= ADPA_DAC_ENABLE | ADPA_HSYNC_CNTL_DISABLE;
54	break;
55    case DPMSModeSuspend:
56	temp |= ADPA_DAC_ENABLE | ADPA_VSYNC_CNTL_DISABLE;
57	break;
58    case DPMSModeOff:
59	temp |= ADPA_HSYNC_CNTL_DISABLE | ADPA_VSYNC_CNTL_DISABLE;
60	break;
61    }
62
63    OUTREG(ADPA, temp);
64}
65
66static void
67i830_crt_save (xf86OutputPtr output)
68{
69    ScrnInfoPtr	pScrn = output->scrn;
70    I830Ptr	pI830 = I830PTR(pScrn);
71
72    pI830->saveADPA = INREG(ADPA);
73}
74
75static void
76i830_crt_restore (xf86OutputPtr output)
77{
78    ScrnInfoPtr	pScrn = output->scrn;
79    I830Ptr	pI830 = I830PTR(pScrn);
80
81    OUTREG(ADPA, pI830->saveADPA);
82}
83
84static int
85i830_crt_mode_valid(xf86OutputPtr output, DisplayModePtr pMode)
86{
87    ScrnInfoPtr pScrn = output->scrn;
88    I830Ptr	pI830 = I830PTR(pScrn);
89    int		maxclock;
90
91    if (pMode->Flags & V_DBLSCAN)
92	return MODE_NO_DBLESCAN;
93
94    if (pMode->Clock < 25000)
95	return MODE_CLOCK_LOW;
96
97    if (!IS_I9XX(pI830))
98	maxclock = 350000;
99    else
100	maxclock = 400000;
101
102    if (pMode->Clock > maxclock)
103	return MODE_CLOCK_HIGH;
104
105    return MODE_OK;
106}
107
108static Bool
109i830_crt_mode_fixup(xf86OutputPtr output, DisplayModePtr mode,
110		    DisplayModePtr adjusted_mode)
111{
112    return TRUE;
113}
114
115static void
116i830_crt_mode_set(xf86OutputPtr output, DisplayModePtr mode,
117		  DisplayModePtr adjusted_mode)
118{
119    ScrnInfoPtr		    pScrn = output->scrn;
120    I830Ptr		    pI830 = I830PTR(pScrn);
121    xf86CrtcPtr		    crtc = output->crtc;
122    I830CrtcPrivatePtr	    i830_crtc = crtc->driver_private;
123    int			    dpll_md_reg;
124    uint32_t		    adpa, dpll_md;
125
126    if (i830_crtc->pipe == 0)
127	dpll_md_reg = DPLL_A_MD;
128    else
129	dpll_md_reg = DPLL_B_MD;
130    /*
131     * Disable separate mode multiplier used when cloning SDVO to CRT
132     * XXX this needs to be adjusted when we really are cloning
133     */
134    if (IS_I965G(pI830))
135    {
136	dpll_md = INREG(dpll_md_reg);
137	OUTREG(dpll_md_reg, dpll_md & ~DPLL_MD_UDI_MULTIPLIER_MASK);
138    }
139
140    adpa = 0;
141    if (adjusted_mode->Flags & V_PHSYNC)
142	adpa |= ADPA_HSYNC_ACTIVE_HIGH;
143    if (adjusted_mode->Flags & V_PVSYNC)
144	adpa |= ADPA_VSYNC_ACTIVE_HIGH;
145
146    if (i830_crtc->pipe == 0)
147    {
148	adpa |= ADPA_PIPE_A_SELECT;
149	OUTREG(BCLRPAT_A, 0);
150    }
151    else
152    {
153	adpa |= ADPA_PIPE_B_SELECT;
154	OUTREG(BCLRPAT_B, 0);
155    }
156
157    OUTREG(ADPA, adpa);
158}
159
160/**
161 * Uses CRT_HOTPLUG_EN and CRT_HOTPLUG_STAT to detect CRT presence.
162 *
163 * Only for I945G/GM.
164 *
165 * \return TRUE if CRT is connected.
166 * \return FALSE if CRT is disconnected.
167 */
168static Bool
169i830_crt_detect_hotplug(xf86OutputPtr output)
170{
171    ScrnInfoPtr	pScrn = output->scrn;
172    I830Ptr	pI830 = I830PTR(pScrn);
173    uint32_t	hotplug_en, temp;
174    const int	timeout_ms = 1000;
175    int		starttime, curtime;
176    int		tries = 1;
177    int		try;
178
179    /* On 4 series desktop, CRT detect sequence need to be done twice
180     * to get a reliable result. */
181    if (IS_G4X(pI830) && !IS_GM45(pI830))
182	tries = 2;
183    else
184	tries = 1;
185
186    hotplug_en = INREG(PORT_HOTPLUG_EN);
187
188    hotplug_en &= CRT_FORCE_HOTPLUG_MASK;
189
190    /* This starts the detection sequence */
191    hotplug_en |= CRT_HOTPLUG_FORCE_DETECT;
192
193    /* GM45 requires a longer activation period to reliably
194     * detect CRT
195     */
196    if (IS_G4X(pI830))
197	hotplug_en |= CRT_HOTPLUG_ACTIVATION_PERIOD_64;
198
199    /* Use the default voltage value */
200    hotplug_en |= CRT_HOTPLUG_VOLTAGE_COMPARE_50;
201
202    for (try = 0; try < tries; try++) {
203	/* turn FORCE_DETECT on */
204	OUTREG(PORT_HOTPLUG_EN, hotplug_en);
205
206	/* wait for FORCE_DETECT to go off */
207	for (curtime = starttime = GetTimeInMillis();
208	     (curtime - starttime) < timeout_ms;
209	     curtime = GetTimeInMillis())
210	{
211	    temp = INREG(PORT_HOTPLUG_EN);
212
213	    if ((temp & CRT_HOTPLUG_FORCE_DETECT) == 0)
214		break;
215	}
216    }
217
218    /* Check the status to see if both blue and green are on now */
219    temp = INREG(PORT_HOTPLUG_STAT);
220    return ((temp & CRT_HOTPLUG_MONITOR_MASK) ==
221	    CRT_HOTPLUG_MONITOR_COLOR);
222}
223
224/**
225 * Detects CRT presence by checking for load.
226 *
227 * Requires that the current pipe's DPLL is active.  This will cause flicker
228 * on the CRT, so it should not be used while the display is being used.  Only
229 * color (not monochrome) displays are detected.
230 *
231 * \return TRUE if CRT is connected.
232 * \return FALSE if CRT is disconnected.
233 */
234static Bool
235i830_crt_detect_load (xf86CrtcPtr	    crtc,
236		      xf86OutputPtr    output)
237{
238    ScrnInfoPtr		    pScrn = output->scrn;
239    I830Ptr		    pI830 = I830PTR(pScrn);
240    I830CrtcPrivatePtr	    i830_crtc = I830CrtcPrivate(crtc);
241    uint32_t		    save_bclrpat;
242    uint32_t		    save_vtotal;
243    uint32_t		    vtotal, vactive;
244    uint32_t		    vsample;
245    uint32_t		    vblank, vblank_start, vblank_end;
246    uint32_t		    dsl;
247    uint8_t		    st00;
248    int			    bclrpat_reg, pipeconf_reg, pipe_dsl_reg;
249    int			    vtotal_reg, vblank_reg, vsync_reg;
250    int			    pipe = i830_crtc->pipe;
251    Bool		    present;
252
253    if (pipe == 0)
254    {
255	bclrpat_reg = BCLRPAT_A;
256	vtotal_reg = VTOTAL_A;
257	vblank_reg = VBLANK_A;
258	vsync_reg = VSYNC_A;
259	pipeconf_reg = PIPEACONF;
260	pipe_dsl_reg = PIPEA_DSL;
261    }
262    else
263    {
264	bclrpat_reg = BCLRPAT_B;
265	vtotal_reg = VTOTAL_B;
266	vblank_reg = VBLANK_B;
267	vsync_reg = VSYNC_B;
268	pipeconf_reg = PIPEBCONF;
269	pipe_dsl_reg = PIPEB_DSL;
270    }
271
272    save_bclrpat = INREG(bclrpat_reg);
273    save_vtotal = INREG(vtotal_reg);
274    vblank = INREG(vblank_reg);
275
276    vtotal = ((save_vtotal >> 16) & 0xfff) + 1;
277    vactive = (save_vtotal & 0x7ff) + 1;
278
279    vblank_start = (vblank & 0xfff) + 1;
280    vblank_end = ((vblank >> 16) & 0xfff) + 1;
281
282    /* Set the border color to purple. */
283    OUTREG(bclrpat_reg, 0x500050);
284
285    if (IS_I9XX (pI830))
286    {
287	uint32_t	pipeconf = INREG(pipeconf_reg);
288	OUTREG(pipeconf_reg, pipeconf | PIPECONF_FORCE_BORDER);
289        /* Wait for next Vblank to substitue border color for Color info */
290        i830WaitForVblank (pScrn);
291	st00 = pI830->readStandard (pI830, 0x3c2);
292	present = (st00 & (1 << 4)) != 0;
293	OUTREG(pipeconf_reg, pipeconf);
294    }
295    else
296    {
297	Bool	restore_vblank = FALSE;
298	int	count, detect;
299
300	/*
301	 * If there isn't any border, add some.
302	 * Yes, this will flicker
303	 */
304	if (vblank_start <= vactive && vblank_end >= vtotal)
305	{
306	    uint32_t  vsync = INREG(vsync_reg);
307	    uint32_t  vsync_start = (vsync & 0xffff) + 1;
308
309	    vblank_start = vsync_start;
310	    OUTREG(vblank_reg, (vblank_start - 1) | ((vblank_end - 1) << 16));
311	    restore_vblank = TRUE;
312	}
313
314	/* sample in the vertical border, selecting the larger one */
315	if (vblank_start - vactive >= vtotal - vblank_end)
316	    vsample = (vblank_start + vactive) >> 1;
317	else
318	    vsample = (vtotal + vblank_end) >> 1;
319
320	/*
321	 * Wait for the border to be displayed
322	 */
323	while (INREG(pipe_dsl_reg) >= vactive)
324	    ;
325	while ((dsl = INREG(pipe_dsl_reg)) <= vsample)
326	    ;
327	/*
328	 * Watch ST00 for an entire scanline
329	 */
330	detect = 0;
331	count = 0;
332	do {
333	    count++;
334	    /* Read the ST00 VGA status register */
335	    st00 = pI830->readStandard(pI830, 0x3c2);
336	    if (st00 & (1 << 4))
337		detect++;
338	} while ((INREG(pipe_dsl_reg) == dsl));
339
340	/* restore vblank if necessary */
341	if (restore_vblank)
342	    OUTREG(vblank_reg, vblank);
343	/*
344	 * If more than 3/4 of the scanline detected a monitor,
345	 * then it is assumed to be present. This works even on i830,
346	 * where there isn't any way to force the border color across
347	 * the screen
348	 */
349	present = detect * 4 > count * 3;
350    }
351
352    /* Restore previous settings */
353    OUTREG(bclrpat_reg, save_bclrpat);
354
355    return present;
356}
357
358/**
359 * Detects CRT presence by probing for a response on the DDC address.
360 *
361 * This takes approximately 5ms in testing on an i915GM, with CRT connected or
362 * not.
363 *
364 * \return TRUE if the CRT is connected and responded to DDC.
365 * \return FALSE if no DDC response was detected.
366 */
367static Bool
368i830_crt_detect_ddc(xf86OutputPtr output)
369{
370    ScrnInfoPtr		    pScrn = output->scrn;
371    I830OutputPrivatePtr    i830_output = output->driver_private;
372    Bool detect;
373
374    /* CRT should always be at 0, but check anyway */
375    if (i830_output->type != I830_OUTPUT_ANALOG)
376	return FALSE;
377
378    I830I2CInit(pScrn, &i830_output->pDDCBus, GPIOA, "CRTDDC_A");
379    detect = xf86I2CProbeAddress(i830_output->pDDCBus, 0x00A0);
380    xf86DestroyI2CBusRec(i830_output->pDDCBus, TRUE, TRUE);
381
382    return detect;
383}
384
385/**
386 * Attempts to detect CRT presence through any method available.
387 *
388 * @param allow_disturb enables detection methods that may cause flickering
389 *        on active displays.
390 */
391static xf86OutputStatus
392i830_crt_detect(xf86OutputPtr output)
393{
394    ScrnInfoPtr		    pScrn = output->scrn;
395    I830Ptr		    pI830 = I830PTR(pScrn);
396    xf86CrtcPtr		    crtc;
397    int			    dpms_mode;
398    xf86OutputStatus	    status;
399    Bool		    connected;
400
401    /*
402     * Try hotplug detection where supported
403     */
404    if (IS_I945G(pI830) || IS_I945GM(pI830) || IS_I965G(pI830) ||
405	    IS_G33CLASS(pI830)) {
406	if (i830_crt_detect_hotplug(output))
407	    status = XF86OutputStatusConnected;
408	else
409	    status = XF86OutputStatusDisconnected;
410
411	goto done;
412    }
413
414    /*
415     * DDC is next best, no flicker
416     */
417    crtc = i830GetLoadDetectPipe (output, NULL, &dpms_mode);
418    if (!crtc)
419	return XF86OutputStatusUnknown;
420
421    if (i830_crt_detect_ddc(output)) {
422	status = XF86OutputStatusConnected;
423	goto out_release_pipe;
424    }
425
426    /* Use the load-detect method if we have no other way of telling. */
427    connected = i830_crt_detect_load (crtc, output);
428    if (connected)
429	status = XF86OutputStatusConnected;
430    else
431	status = XF86OutputStatusDisconnected;
432
433out_release_pipe:
434    i830ReleaseLoadDetectPipe (output, dpms_mode);
435
436done:
437    return status;
438}
439
440static void
441i830_crt_destroy (xf86OutputPtr output)
442{
443    if (output->driver_private)
444	xfree (output->driver_private);
445}
446
447#ifdef RANDR_GET_CRTC_INTERFACE
448static xf86CrtcPtr
449i830_crt_get_crtc(xf86OutputPtr output)
450{
451    ScrnInfoPtr	pScrn = output->scrn;
452    I830Ptr pI830 = I830PTR(pScrn);
453    int pipe = !!(INREG(ADPA) & ADPA_PIPE_SELECT_MASK);
454
455    return i830_pipe_to_crtc(pScrn, pipe);
456}
457#endif
458
459static xf86MonPtr
460i830_get_edid(xf86OutputPtr output, int gpio_reg, char *gpio_str)
461{
462    I830OutputPrivatePtr    intel_output = output->driver_private;
463    xf86MonPtr		    edid_mon = NULL;
464
465    /* Set up the DDC bus. */
466    I830I2CInit(output->scrn, &intel_output->pDDCBus, gpio_reg, gpio_str);
467
468    edid_mon = xf86OutputGetEDID (output, intel_output->pDDCBus);
469
470    if (!edid_mon || DIGITAL(edid_mon->features.input_type)) {
471	xf86DestroyI2CBusRec(intel_output->pDDCBus, TRUE, TRUE);
472	intel_output->pDDCBus = NULL;
473	if (edid_mon) {
474	    xfree(edid_mon);
475	    edid_mon = NULL;
476	}
477    }
478
479    return edid_mon;
480}
481
482static DisplayModePtr
483i830_crt_get_modes (xf86OutputPtr output)
484{
485    DisplayModePtr	    modes;
486    xf86MonPtr		    edid_mon = NULL;
487    I830OutputPrivatePtr    intel_output = output->driver_private;
488
489    /* Try to probe normal CRT port, and also digital port for output
490       in DVI-I mode. */
491    if ((edid_mon = i830_get_edid(output, GPIOA, "CRTDDC_A")))
492	goto found;
493    if ((edid_mon = i830_get_edid(output, GPIOD, "CRTDDC_D")))
494	goto found;
495    if ((edid_mon = i830_get_edid(output, GPIOE, "CRTDDC_E")))
496	goto found;
497found:
498    /* Destroy DDC bus after probe, so every other new probe will
499       scan all ports again */
500    if (intel_output->pDDCBus)
501	xf86DestroyI2CBusRec(intel_output->pDDCBus, TRUE, TRUE);
502
503    xf86OutputSetEDID (output, edid_mon);
504
505    modes = xf86OutputGetEDIDModes (output);
506    return modes;
507}
508
509static const xf86OutputFuncsRec i830_crt_output_funcs = {
510    .dpms = i830_crt_dpms,
511    .save = i830_crt_save,
512    .restore = i830_crt_restore,
513    .mode_valid = i830_crt_mode_valid,
514    .mode_fixup = i830_crt_mode_fixup,
515    .prepare = i830_output_prepare,
516    .mode_set = i830_crt_mode_set,
517    .commit = i830_output_commit,
518    .detect = i830_crt_detect,
519    .get_modes = i830_crt_get_modes,
520    .destroy = i830_crt_destroy,
521#ifdef RANDR_GET_CRTC_INTERFACE
522    .get_crtc = i830_crt_get_crtc,
523#endif
524};
525
526void
527i830_crt_init(ScrnInfoPtr pScrn)
528{
529    xf86OutputPtr	    output;
530    I830OutputPrivatePtr    i830_output;
531    I830Ptr		    pI830 = I830PTR(pScrn);
532
533    if (pI830->quirk_flag & QUIRK_IGNORE_CRT)
534	return;
535
536    output = xf86OutputCreate (pScrn, &i830_crt_output_funcs, "VGA");
537    if (!output)
538	return;
539    i830_output = xnfcalloc (sizeof (I830OutputPrivateRec), 1);
540    if (!i830_output)
541    {
542	xf86OutputDestroy (output);
543	return;
544    }
545    i830_output->type = I830_OUTPUT_ANALOG;
546    /* i830 (almador) cannot place the analog adaptor on pipe B */
547    if (IS_I830(pI830))
548	i830_output->pipe_mask = (1 << 0);
549    else
550	i830_output->pipe_mask = ((1 << 0) | (1 << 1));
551    i830_output->clone_mask = ((1 << I830_OUTPUT_ANALOG) |
552			       (1 << I830_OUTPUT_DVO_TMDS));
553
554    output->driver_private = i830_output;
555    output->interlaceAllowed = FALSE;
556    output->doubleScanAllowed = FALSE;
557}
558