1fa225cbcSrjs/*
2fa225cbcSrjs * Copyright © 2007 Intel Corporation
3fa225cbcSrjs *
4fa225cbcSrjs * Permission is hereby granted, free of charge, to any person obtaining a
5fa225cbcSrjs * copy of this software and associated documentation files (the "Software"),
6fa225cbcSrjs * to deal in the Software without restriction, including without limitation
7fa225cbcSrjs * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8fa225cbcSrjs * and/or sell copies of the Software, and to permit persons to whom the
9fa225cbcSrjs * Software is furnished to do so, subject to the following conditions:
10fa225cbcSrjs *
11fa225cbcSrjs * The above copyright notice and this permission notice (including the next
12fa225cbcSrjs * paragraph) shall be included in all copies or substantial portions of the
13fa225cbcSrjs * Software.
14fa225cbcSrjs *
15fa225cbcSrjs * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16fa225cbcSrjs * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17fa225cbcSrjs * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18fa225cbcSrjs * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19fa225cbcSrjs * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20fa225cbcSrjs * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21fa225cbcSrjs * IN THE SOFTWARE.
22fa225cbcSrjs *
23fa225cbcSrjs * Authors:
24fa225cbcSrjs *    Eric Anholt <eric@anholt.net>
25fa225cbcSrjs *
26fa225cbcSrjs */
27fa225cbcSrjs
28fa225cbcSrjs#ifdef HAVE_CONFIG_H
29fa225cbcSrjs#include "config.h"
30fa225cbcSrjs#endif
31fa225cbcSrjs
32fa225cbcSrjs#include "xf86.h"
33fa225cbcSrjs#include "i830.h"
34fa225cbcSrjs#include "xf86Modes.h"
35fa225cbcSrjs#include "i830_display.h"
36fa225cbcSrjs#include "X11/Xatom.h"
37fa225cbcSrjs
38fa225cbcSrjsstruct i830_hdmi_priv {
39fa225cbcSrjs    uint32_t output_reg;
40fa225cbcSrjs
41fa225cbcSrjs    uint32_t save_SDVO;
42fa225cbcSrjs
43fa225cbcSrjs    Bool has_hdmi_sink;
44fa225cbcSrjs    /* Default 0 for full RGB range 0-255, 1 is for RGB range 16-235 */
45fa225cbcSrjs    uint32_t broadcast_rgb;
46fa225cbcSrjs};
47fa225cbcSrjs
48fa225cbcSrjsstatic Atom broadcast_atom;
49fa225cbcSrjs
50fa225cbcSrjsstatic int
51fa225cbcSrjsi830_hdmi_mode_valid(xf86OutputPtr output, DisplayModePtr mode)
52fa225cbcSrjs{
53fa225cbcSrjs    if (mode->Clock > 165000)
54fa225cbcSrjs	return MODE_CLOCK_HIGH;
55fa225cbcSrjs
56fa225cbcSrjs    if (mode->Clock < 20000)
57fa225cbcSrjs	return MODE_CLOCK_LOW;
58fa225cbcSrjs
59fa225cbcSrjs    return MODE_OK;
60fa225cbcSrjs}
61fa225cbcSrjs
62fa225cbcSrjsstatic Bool
63fa225cbcSrjsi830_hdmi_mode_fixup(xf86OutputPtr output, DisplayModePtr mode,
64fa225cbcSrjs		     DisplayModePtr adjusted_mode)
65fa225cbcSrjs{
66fa225cbcSrjs    /* The HDMI output doesn't need the pixel multiplication that SDVO does,
67fa225cbcSrjs     * so no fixup.
68fa225cbcSrjs     */
69fa225cbcSrjs    return TRUE;
70fa225cbcSrjs}
71fa225cbcSrjs
72fa225cbcSrjsstatic void
73fa225cbcSrjsi830_hdmi_mode_set(xf86OutputPtr output, DisplayModePtr mode,
74fa225cbcSrjs		   DisplayModePtr adjusted_mode)
75fa225cbcSrjs{
76fa225cbcSrjs    ScrnInfoPtr pScrn = output->scrn;
77fa225cbcSrjs    I830OutputPrivatePtr intel_output = output->driver_private;
78fa225cbcSrjs    struct i830_hdmi_priv *dev_priv = intel_output->dev_priv;
79fa225cbcSrjs    I830Ptr pI830 = I830PTR(pScrn);
80fa225cbcSrjs    xf86CrtcPtr crtc = output->crtc;
81fa225cbcSrjs    I830CrtcPrivatePtr intel_crtc = crtc->driver_private;
82fa225cbcSrjs    uint32_t sdvox;
83fa225cbcSrjs
84fa225cbcSrjs    sdvox = SDVO_ENCODING_HDMI |
85fa225cbcSrjs	SDVO_BORDER_ENABLE |
86fa225cbcSrjs	SDVO_VSYNC_ACTIVE_HIGH |
87fa225cbcSrjs	SDVO_HSYNC_ACTIVE_HIGH;
88fa225cbcSrjs
89fa225cbcSrjs    if (dev_priv->has_hdmi_sink)
90fa225cbcSrjs	    sdvox |= SDVO_AUDIO_ENABLE;
91fa225cbcSrjs
92fa225cbcSrjs    if (intel_crtc->pipe == 1)
93fa225cbcSrjs	sdvox |= SDVO_PIPE_B_SELECT;
94fa225cbcSrjs
95fa225cbcSrjs    OUTREG(dev_priv->output_reg, sdvox);
96fa225cbcSrjs    POSTING_READ(dev_priv->output_reg);
97fa225cbcSrjs}
98fa225cbcSrjs
99fa225cbcSrjsstatic void
100fa225cbcSrjsi830_hdmi_dpms(xf86OutputPtr output, int mode)
101fa225cbcSrjs{
102fa225cbcSrjs    ScrnInfoPtr pScrn = output->scrn;
103fa225cbcSrjs    I830OutputPrivatePtr intel_output = output->driver_private;
104fa225cbcSrjs    struct i830_hdmi_priv *dev_priv = intel_output->dev_priv;
105fa225cbcSrjs    I830Ptr pI830 = I830PTR(pScrn);
106fa225cbcSrjs    uint32_t  temp;
107fa225cbcSrjs
108fa225cbcSrjs    if (mode == DPMSModeOff) {
109fa225cbcSrjs	temp = INREG(dev_priv->output_reg);
110fa225cbcSrjs	OUTREG(dev_priv->output_reg, temp & ~SDVO_ENABLE);
111fa225cbcSrjs    } else {
112fa225cbcSrjs	temp = INREG(dev_priv->output_reg);
113fa225cbcSrjs	OUTREG(dev_priv->output_reg, temp | SDVO_ENABLE);
114fa225cbcSrjs    }
115fa225cbcSrjs}
116fa225cbcSrjs
117fa225cbcSrjsstatic void
118fa225cbcSrjsi830_hdmi_save(xf86OutputPtr output)
119fa225cbcSrjs{
120fa225cbcSrjs    ScrnInfoPtr pScrn = output->scrn;
121fa225cbcSrjs    I830OutputPrivatePtr intel_output = output->driver_private;
122fa225cbcSrjs    struct i830_hdmi_priv *dev_priv = intel_output->dev_priv;
123fa225cbcSrjs    I830Ptr pI830 = I830PTR(pScrn);
124fa225cbcSrjs
125fa225cbcSrjs    dev_priv->save_SDVO = INREG(dev_priv->output_reg);
126fa225cbcSrjs}
127fa225cbcSrjs
128fa225cbcSrjsstatic void
129fa225cbcSrjsi830_hdmi_restore(xf86OutputPtr output)
130fa225cbcSrjs{
131fa225cbcSrjs    ScrnInfoPtr pScrn = output->scrn;
132fa225cbcSrjs    I830OutputPrivatePtr intel_output = output->driver_private;
133fa225cbcSrjs    struct i830_hdmi_priv *dev_priv = intel_output->dev_priv;
134fa225cbcSrjs    I830Ptr pI830 = I830PTR(pScrn);
135fa225cbcSrjs
136fa225cbcSrjs    OUTREG(dev_priv->output_reg, dev_priv->save_SDVO);
137fa225cbcSrjs}
138fa225cbcSrjs
139fa225cbcSrjs/**
140fa225cbcSrjs * Uses CRT_HOTPLUG_EN and CRT_HOTPLUG_STAT to detect HDMI connection.
141fa225cbcSrjs *
142fa225cbcSrjs * \return TRUE if HDMI port is connected.
143fa225cbcSrjs * \return FALSE if HDMI port is disconnected.
144fa225cbcSrjs */
145fa225cbcSrjsstatic xf86OutputStatus
146fa225cbcSrjsi830_hdmi_detect(xf86OutputPtr output)
147fa225cbcSrjs{
148fa225cbcSrjs    ScrnInfoPtr	pScrn = output->scrn;
149fa225cbcSrjs    I830OutputPrivatePtr intel_output = output->driver_private;
150fa225cbcSrjs    struct i830_hdmi_priv *dev_priv = intel_output->dev_priv;
151fa225cbcSrjs    I830Ptr pI830 = I830PTR(pScrn);
152fa225cbcSrjs    uint32_t temp, bit;
153fa225cbcSrjs    xf86OutputStatus status;
154fa225cbcSrjs    xf86MonPtr edid_mon;
155fa225cbcSrjs
156fa225cbcSrjs    dev_priv->has_hdmi_sink = FALSE;
157fa225cbcSrjs
158fa225cbcSrjs    /* For G4X desktop chip, PEG_BAND_GAP_DATA 3:0 must first be written 0xd.
159fa225cbcSrjs     * Failure to do so will result in spurious interrupts being
160fa225cbcSrjs     * generated on the port when a cable is not attached.
161fa225cbcSrjs     */
162fa225cbcSrjs    if (IS_G4X(pI830) && !IS_GM45(pI830)) {
163fa225cbcSrjs	temp = INREG(PEG_BAND_GAP_DATA);
164fa225cbcSrjs	OUTREG(PEG_BAND_GAP_DATA, (temp & ~0xf) | 0xd);
165fa225cbcSrjs    }
166fa225cbcSrjs
167fa225cbcSrjs    temp = INREG(PORT_HOTPLUG_EN);
168fa225cbcSrjs
169fa225cbcSrjs    switch (dev_priv->output_reg) {
170fa225cbcSrjs    case SDVOB:
171fa225cbcSrjs	temp |= HDMIB_HOTPLUG_INT_EN;
172fa225cbcSrjs	break;
173fa225cbcSrjs    case SDVOC:
174fa225cbcSrjs	temp |= HDMIC_HOTPLUG_INT_EN;
175fa225cbcSrjs	break;
176fa225cbcSrjs    default:
177fa225cbcSrjs	return XF86OutputStatusUnknown;
178fa225cbcSrjs    }
179fa225cbcSrjs
180fa225cbcSrjs    OUTREG(PORT_HOTPLUG_EN, temp);
181fa225cbcSrjs
182fa225cbcSrjs    POSTING_READ(PORT_HOTPLUG_EN);
183fa225cbcSrjs
184fa225cbcSrjs    i830WaitForVblank(pScrn);
185fa225cbcSrjs    switch (dev_priv->output_reg) {
186fa225cbcSrjs    case SDVOB:
187fa225cbcSrjs	bit = HDMIB_HOTPLUG_INT_STATUS;
188fa225cbcSrjs	break;
189fa225cbcSrjs    case SDVOC:
190fa225cbcSrjs	bit = HDMIC_HOTPLUG_INT_STATUS;
191fa225cbcSrjs	break;
192fa225cbcSrjs    default:
193fa225cbcSrjs	return XF86OutputStatusUnknown;
194fa225cbcSrjs    }
195fa225cbcSrjs
196fa225cbcSrjs    if ((INREG(PORT_HOTPLUG_STAT) & bit) != 0)
197fa225cbcSrjs	status = XF86OutputStatusConnected;
198fa225cbcSrjs    else
199fa225cbcSrjs	return XF86OutputStatusDisconnected;
200fa225cbcSrjs
201fa225cbcSrjs    edid_mon = xf86OutputGetEDID (output, intel_output->pDDCBus);
202fa225cbcSrjs    if (!edid_mon || !DIGITAL(edid_mon->features.input_type))
203fa225cbcSrjs	status = XF86OutputStatusDisconnected;
204fa225cbcSrjs
205fa225cbcSrjs    if (xf86LoaderCheckSymbol("xf86MonitorIsHDMI") &&
206fa225cbcSrjs	    xf86MonitorIsHDMI(edid_mon))
207fa225cbcSrjs	dev_priv->has_hdmi_sink = TRUE;
208fa225cbcSrjs
209fa225cbcSrjs    if (pI830->debug_modes)
210fa225cbcSrjs	xf86DrvMsg(pScrn->scrnIndex, X_INFO,
211fa225cbcSrjs			"%s monitor detected on HDMI-%d\n",
212fa225cbcSrjs			dev_priv->has_hdmi_sink ? "HDMI" : "DVI",
213fa225cbcSrjs			(dev_priv->output_reg == SDVOB) ? 1 : 2);
214fa225cbcSrjs
215fa225cbcSrjs    xfree(edid_mon);
216fa225cbcSrjs    return status;
217fa225cbcSrjs}
218fa225cbcSrjs
219fa225cbcSrjsstatic void
220fa225cbcSrjsi830_hdmi_destroy (xf86OutputPtr output)
221fa225cbcSrjs{
222fa225cbcSrjs    I830OutputPrivatePtr intel_output = output->driver_private;
223fa225cbcSrjs
224fa225cbcSrjs    if (intel_output != NULL) {
225fa225cbcSrjs	xf86DestroyI2CBusRec(intel_output->pDDCBus, FALSE, FALSE);
226fa225cbcSrjs	xfree(intel_output);
227fa225cbcSrjs    }
228fa225cbcSrjs}
229fa225cbcSrjs
230fa225cbcSrjsstatic void
231fa225cbcSrjsi830_hdmi_create_resources(xf86OutputPtr output)
232fa225cbcSrjs{
233fa225cbcSrjs    ScrnInfoPtr                 pScrn = output->scrn;
234fa225cbcSrjs    I830Ptr                     pI830 = I830PTR(pScrn);
235fa225cbcSrjs    I830OutputPrivatePtr        intel_output = output->driver_private;
236fa225cbcSrjs    struct i830_hdmi_priv       *dev_priv = intel_output->dev_priv;
237fa225cbcSrjs    INT32			broadcast_range[2];
238fa225cbcSrjs    int                         err;
239fa225cbcSrjs
240fa225cbcSrjs    /* only R G B are 8bit color mode */
241fa225cbcSrjs    if (pScrn->depth != 24 ||
242fa225cbcSrjs        /* only 965G and G4X platform */
243fa225cbcSrjs        !(IS_I965G(pI830) || IS_G4X(pI830)))
244fa225cbcSrjs        return;
245fa225cbcSrjs
246fa225cbcSrjs    broadcast_atom =
247fa225cbcSrjs        MakeAtom("BROADCAST_RGB", sizeof("BROADCAST_RGB") - 1, TRUE);
248fa225cbcSrjs
249fa225cbcSrjs    broadcast_range[0] = 0;
250fa225cbcSrjs    broadcast_range[1] = 1;
251fa225cbcSrjs    err = RRConfigureOutputProperty(output->randr_output,
252fa225cbcSrjs                                    broadcast_atom,
253fa225cbcSrjs                                    FALSE, TRUE, FALSE, 2, broadcast_range);
254fa225cbcSrjs    if (err != 0) {
255fa225cbcSrjs        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
256fa225cbcSrjs                   "RRConfigureOutputProperty error, %d\n", err);
257fa225cbcSrjs        return;
258fa225cbcSrjs    }
259fa225cbcSrjs    /* Set the current value of the broadcast property as full range */
260fa225cbcSrjs    dev_priv->broadcast_rgb = 0;
261fa225cbcSrjs    err = RRChangeOutputProperty(output->randr_output,
262fa225cbcSrjs                                 broadcast_atom,
263fa225cbcSrjs                                 XA_INTEGER, 32, PropModeReplace,
264fa225cbcSrjs                                 1, &dev_priv->broadcast_rgb,
265fa225cbcSrjs                                 FALSE, TRUE);
266fa225cbcSrjs    if (err != 0) {
267fa225cbcSrjs        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
268fa225cbcSrjs                   "RRChangeOutputProperty error, %d\n", err);
269fa225cbcSrjs        return;
270fa225cbcSrjs    }
271fa225cbcSrjs}
272fa225cbcSrjs
273fa225cbcSrjsstatic Bool
274fa225cbcSrjsi830_hdmi_set_property(xf86OutputPtr output, Atom property,
275fa225cbcSrjs                       RRPropertyValuePtr value)
276fa225cbcSrjs{
277fa225cbcSrjs    ScrnInfoPtr             pScrn = output->scrn;
278fa225cbcSrjs    I830Ptr                 pI830 = I830PTR(pScrn);
279fa225cbcSrjs    I830OutputPrivatePtr    intel_output = output->driver_private;
280fa225cbcSrjs    struct i830_hdmi_priv   *dev_priv = intel_output->dev_priv;
281fa225cbcSrjs    uint32_t temp;
282fa225cbcSrjs
283fa225cbcSrjs    if (property == broadcast_atom) {
284fa225cbcSrjs        uint32_t val;
285fa225cbcSrjs
286fa225cbcSrjs        if (value->type != XA_INTEGER || value->format != 32 ||
287fa225cbcSrjs            value->size != 1)
288fa225cbcSrjs        {
289fa225cbcSrjs            return FALSE;
290fa225cbcSrjs        }
291fa225cbcSrjs
292fa225cbcSrjs        val = *(INT32 *)value->data;
293fa225cbcSrjs        if (val < 0 || val > 1)
294fa225cbcSrjs        {
295fa225cbcSrjs            return FALSE;
296fa225cbcSrjs        }
297fa225cbcSrjs        if (val == dev_priv->broadcast_rgb)
298fa225cbcSrjs            return TRUE;
299fa225cbcSrjs
300fa225cbcSrjs        temp = INREG(dev_priv->output_reg);
301fa225cbcSrjs
302fa225cbcSrjs        if (val == 1)
303fa225cbcSrjs            temp |= SDVO_COLOR_NOT_FULL_RANGE;
304fa225cbcSrjs        else if (val == 0)
305fa225cbcSrjs            temp &= ~SDVO_COLOR_NOT_FULL_RANGE;
306fa225cbcSrjs
307fa225cbcSrjs        OUTREG(dev_priv->output_reg, temp);
308fa225cbcSrjs        dev_priv->broadcast_rgb = val;
309fa225cbcSrjs    }
310fa225cbcSrjs    return TRUE;
311fa225cbcSrjs}
312fa225cbcSrjs
313fa225cbcSrjsstatic const xf86OutputFuncsRec i830_hdmi_output_funcs = {
314fa225cbcSrjs    .create_resources = i830_hdmi_create_resources,
315fa225cbcSrjs    .dpms = i830_hdmi_dpms,
316fa225cbcSrjs    .save = i830_hdmi_save,
317fa225cbcSrjs    .restore = i830_hdmi_restore,
318fa225cbcSrjs    .mode_valid = i830_hdmi_mode_valid,
319fa225cbcSrjs    .mode_fixup = i830_hdmi_mode_fixup,
320fa225cbcSrjs    .prepare = i830_output_prepare,
321fa225cbcSrjs    .mode_set = i830_hdmi_mode_set,
322fa225cbcSrjs    .commit = i830_output_commit,
323fa225cbcSrjs    .detect = i830_hdmi_detect,
324fa225cbcSrjs    .get_modes = i830_ddc_get_modes,
325fa225cbcSrjs    .set_property = i830_hdmi_set_property,
326fa225cbcSrjs    .destroy = i830_hdmi_destroy
327fa225cbcSrjs};
328fa225cbcSrjs
329fa225cbcSrjsvoid
330fa225cbcSrjsi830_hdmi_init(ScrnInfoPtr pScrn, int output_reg)
331fa225cbcSrjs{
332fa225cbcSrjs    xf86OutputPtr output;
333fa225cbcSrjs    I830OutputPrivatePtr intel_output;
334fa225cbcSrjs    struct i830_hdmi_priv *dev_priv;
335fa225cbcSrjs
336fa225cbcSrjs    output = xf86OutputCreate(pScrn, &i830_hdmi_output_funcs,
337fa225cbcSrjs			      (output_reg == SDVOB) ? "HDMI-1" : "HDMI-2");
338fa225cbcSrjs    if (!output)
339fa225cbcSrjs	return;
340fa225cbcSrjs    intel_output = xnfcalloc(sizeof (I830OutputPrivateRec) +
341fa225cbcSrjs			     sizeof (struct i830_hdmi_priv), 1);
342fa225cbcSrjs    if (intel_output == NULL) {
343fa225cbcSrjs	xf86OutputDestroy(output);
344fa225cbcSrjs	return;
345fa225cbcSrjs    }
346fa225cbcSrjs    output->driver_private = intel_output;
347fa225cbcSrjs    output->interlaceAllowed = FALSE;
348fa225cbcSrjs    output->doubleScanAllowed = FALSE;
349fa225cbcSrjs
350fa225cbcSrjs    dev_priv = (struct i830_hdmi_priv *)(intel_output + 1);
351fa225cbcSrjs    dev_priv->output_reg = output_reg;
352fa225cbcSrjs    dev_priv->has_hdmi_sink = FALSE;
353fa225cbcSrjs
354fa225cbcSrjs    intel_output->dev_priv = dev_priv;
355fa225cbcSrjs    intel_output->type = I830_OUTPUT_HDMI;
356fa225cbcSrjs    intel_output->pipe_mask = ((1 << 0) | (1 << 1));
357fa225cbcSrjs    intel_output->clone_mask = (1 << I830_OUTPUT_HDMI);
358fa225cbcSrjs
359fa225cbcSrjs    /* Set up the DDC bus. */
360fa225cbcSrjs    if (output_reg == SDVOB)
361fa225cbcSrjs	I830I2CInit(pScrn, &intel_output->pDDCBus, GPIOE, "HDMIDDC_B");
362fa225cbcSrjs    else
363fa225cbcSrjs	I830I2CInit(pScrn, &intel_output->pDDCBus, GPIOD, "HDMIDDC_C");
364fa225cbcSrjs
365fa225cbcSrjs    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
366fa225cbcSrjs	       "HDMI output %d detected\n",
367fa225cbcSrjs	       (output_reg == SDVOB) ? 1 : 2);
368fa225cbcSrjs}
369