1/*
2 * Copyright © 2007 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 DEALINGS
21 * 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#include "X11/Xatom.h"
37
38struct i830_hdmi_priv {
39    uint32_t output_reg;
40
41    uint32_t save_SDVO;
42
43    Bool has_hdmi_sink;
44    /* Default 0 for full RGB range 0-255, 1 is for RGB range 16-235 */
45    uint32_t broadcast_rgb;
46};
47
48static Atom broadcast_atom;
49
50static int
51i830_hdmi_mode_valid(xf86OutputPtr output, DisplayModePtr mode)
52{
53    if (mode->Clock > 165000)
54	return MODE_CLOCK_HIGH;
55
56    if (mode->Clock < 20000)
57	return MODE_CLOCK_LOW;
58
59    return MODE_OK;
60}
61
62static Bool
63i830_hdmi_mode_fixup(xf86OutputPtr output, DisplayModePtr mode,
64		     DisplayModePtr adjusted_mode)
65{
66    /* The HDMI output doesn't need the pixel multiplication that SDVO does,
67     * so no fixup.
68     */
69    return TRUE;
70}
71
72static void
73i830_hdmi_mode_set(xf86OutputPtr output, DisplayModePtr mode,
74		   DisplayModePtr adjusted_mode)
75{
76    ScrnInfoPtr pScrn = output->scrn;
77    I830OutputPrivatePtr intel_output = output->driver_private;
78    struct i830_hdmi_priv *dev_priv = intel_output->dev_priv;
79    I830Ptr pI830 = I830PTR(pScrn);
80    xf86CrtcPtr crtc = output->crtc;
81    I830CrtcPrivatePtr intel_crtc = crtc->driver_private;
82    uint32_t sdvox;
83
84    sdvox = SDVO_ENCODING_HDMI |
85	SDVO_BORDER_ENABLE |
86	SDVO_VSYNC_ACTIVE_HIGH |
87	SDVO_HSYNC_ACTIVE_HIGH;
88
89    if (dev_priv->has_hdmi_sink)
90	    sdvox |= SDVO_AUDIO_ENABLE;
91
92    if (intel_crtc->pipe == 1)
93	sdvox |= SDVO_PIPE_B_SELECT;
94
95    OUTREG(dev_priv->output_reg, sdvox);
96    POSTING_READ(dev_priv->output_reg);
97}
98
99static void
100i830_hdmi_dpms(xf86OutputPtr output, int mode)
101{
102    ScrnInfoPtr pScrn = output->scrn;
103    I830OutputPrivatePtr intel_output = output->driver_private;
104    struct i830_hdmi_priv *dev_priv = intel_output->dev_priv;
105    I830Ptr pI830 = I830PTR(pScrn);
106    uint32_t  temp;
107
108    if (mode == DPMSModeOff) {
109	temp = INREG(dev_priv->output_reg);
110	OUTREG(dev_priv->output_reg, temp & ~SDVO_ENABLE);
111    } else {
112	temp = INREG(dev_priv->output_reg);
113	OUTREG(dev_priv->output_reg, temp | SDVO_ENABLE);
114    }
115}
116
117static void
118i830_hdmi_save(xf86OutputPtr output)
119{
120    ScrnInfoPtr pScrn = output->scrn;
121    I830OutputPrivatePtr intel_output = output->driver_private;
122    struct i830_hdmi_priv *dev_priv = intel_output->dev_priv;
123    I830Ptr pI830 = I830PTR(pScrn);
124
125    dev_priv->save_SDVO = INREG(dev_priv->output_reg);
126}
127
128static void
129i830_hdmi_restore(xf86OutputPtr output)
130{
131    ScrnInfoPtr pScrn = output->scrn;
132    I830OutputPrivatePtr intel_output = output->driver_private;
133    struct i830_hdmi_priv *dev_priv = intel_output->dev_priv;
134    I830Ptr pI830 = I830PTR(pScrn);
135
136    OUTREG(dev_priv->output_reg, dev_priv->save_SDVO);
137}
138
139/**
140 * Uses CRT_HOTPLUG_EN and CRT_HOTPLUG_STAT to detect HDMI connection.
141 *
142 * \return TRUE if HDMI port is connected.
143 * \return FALSE if HDMI port is disconnected.
144 */
145static xf86OutputStatus
146i830_hdmi_detect(xf86OutputPtr output)
147{
148    ScrnInfoPtr	pScrn = output->scrn;
149    I830OutputPrivatePtr intel_output = output->driver_private;
150    struct i830_hdmi_priv *dev_priv = intel_output->dev_priv;
151    I830Ptr pI830 = I830PTR(pScrn);
152    uint32_t temp, bit;
153    xf86OutputStatus status;
154    xf86MonPtr edid_mon;
155
156    dev_priv->has_hdmi_sink = FALSE;
157
158    /* For G4X desktop chip, PEG_BAND_GAP_DATA 3:0 must first be written 0xd.
159     * Failure to do so will result in spurious interrupts being
160     * generated on the port when a cable is not attached.
161     */
162    if (IS_G4X(pI830) && !IS_GM45(pI830)) {
163	temp = INREG(PEG_BAND_GAP_DATA);
164	OUTREG(PEG_BAND_GAP_DATA, (temp & ~0xf) | 0xd);
165    }
166
167    temp = INREG(PORT_HOTPLUG_EN);
168
169    switch (dev_priv->output_reg) {
170    case SDVOB:
171	temp |= HDMIB_HOTPLUG_INT_EN;
172	break;
173    case SDVOC:
174	temp |= HDMIC_HOTPLUG_INT_EN;
175	break;
176    default:
177	return XF86OutputStatusUnknown;
178    }
179
180    OUTREG(PORT_HOTPLUG_EN, temp);
181
182    POSTING_READ(PORT_HOTPLUG_EN);
183
184    i830WaitForVblank(pScrn);
185    switch (dev_priv->output_reg) {
186    case SDVOB:
187	bit = HDMIB_HOTPLUG_INT_STATUS;
188	break;
189    case SDVOC:
190	bit = HDMIC_HOTPLUG_INT_STATUS;
191	break;
192    default:
193	return XF86OutputStatusUnknown;
194    }
195
196    if ((INREG(PORT_HOTPLUG_STAT) & bit) != 0)
197	status = XF86OutputStatusConnected;
198    else
199	return XF86OutputStatusDisconnected;
200
201    edid_mon = xf86OutputGetEDID (output, intel_output->pDDCBus);
202    if (!edid_mon || !DIGITAL(edid_mon->features.input_type))
203	status = XF86OutputStatusDisconnected;
204
205    if (xf86LoaderCheckSymbol("xf86MonitorIsHDMI") &&
206	    xf86MonitorIsHDMI(edid_mon))
207	dev_priv->has_hdmi_sink = TRUE;
208
209    if (pI830->debug_modes)
210	xf86DrvMsg(pScrn->scrnIndex, X_INFO,
211			"%s monitor detected on HDMI-%d\n",
212			dev_priv->has_hdmi_sink ? "HDMI" : "DVI",
213			(dev_priv->output_reg == SDVOB) ? 1 : 2);
214
215    xfree(edid_mon);
216    return status;
217}
218
219static void
220i830_hdmi_destroy (xf86OutputPtr output)
221{
222    I830OutputPrivatePtr intel_output = output->driver_private;
223
224    if (intel_output != NULL) {
225	xf86DestroyI2CBusRec(intel_output->pDDCBus, FALSE, FALSE);
226	xfree(intel_output);
227    }
228}
229
230static void
231i830_hdmi_create_resources(xf86OutputPtr output)
232{
233    ScrnInfoPtr                 pScrn = output->scrn;
234    I830Ptr                     pI830 = I830PTR(pScrn);
235    I830OutputPrivatePtr        intel_output = output->driver_private;
236    struct i830_hdmi_priv       *dev_priv = intel_output->dev_priv;
237    INT32			broadcast_range[2];
238    int                         err;
239
240    /* only R G B are 8bit color mode */
241    if (pScrn->depth != 24 ||
242        /* only 965G and G4X platform */
243        !(IS_I965G(pI830) || IS_G4X(pI830)))
244        return;
245
246    broadcast_atom =
247        MakeAtom("BROADCAST_RGB", sizeof("BROADCAST_RGB") - 1, TRUE);
248
249    broadcast_range[0] = 0;
250    broadcast_range[1] = 1;
251    err = RRConfigureOutputProperty(output->randr_output,
252                                    broadcast_atom,
253                                    FALSE, TRUE, FALSE, 2, broadcast_range);
254    if (err != 0) {
255        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
256                   "RRConfigureOutputProperty error, %d\n", err);
257        return;
258    }
259    /* Set the current value of the broadcast property as full range */
260    dev_priv->broadcast_rgb = 0;
261    err = RRChangeOutputProperty(output->randr_output,
262                                 broadcast_atom,
263                                 XA_INTEGER, 32, PropModeReplace,
264                                 1, &dev_priv->broadcast_rgb,
265                                 FALSE, TRUE);
266    if (err != 0) {
267        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
268                   "RRChangeOutputProperty error, %d\n", err);
269        return;
270    }
271}
272
273static Bool
274i830_hdmi_set_property(xf86OutputPtr output, Atom property,
275                       RRPropertyValuePtr value)
276{
277    ScrnInfoPtr             pScrn = output->scrn;
278    I830Ptr                 pI830 = I830PTR(pScrn);
279    I830OutputPrivatePtr    intel_output = output->driver_private;
280    struct i830_hdmi_priv   *dev_priv = intel_output->dev_priv;
281    uint32_t temp;
282
283    if (property == broadcast_atom) {
284        uint32_t val;
285
286        if (value->type != XA_INTEGER || value->format != 32 ||
287            value->size != 1)
288        {
289            return FALSE;
290        }
291
292        val = *(INT32 *)value->data;
293        if (val < 0 || val > 1)
294        {
295            return FALSE;
296        }
297        if (val == dev_priv->broadcast_rgb)
298            return TRUE;
299
300        temp = INREG(dev_priv->output_reg);
301
302        if (val == 1)
303            temp |= SDVO_COLOR_NOT_FULL_RANGE;
304        else if (val == 0)
305            temp &= ~SDVO_COLOR_NOT_FULL_RANGE;
306
307        OUTREG(dev_priv->output_reg, temp);
308        dev_priv->broadcast_rgb = val;
309    }
310    return TRUE;
311}
312
313static const xf86OutputFuncsRec i830_hdmi_output_funcs = {
314    .create_resources = i830_hdmi_create_resources,
315    .dpms = i830_hdmi_dpms,
316    .save = i830_hdmi_save,
317    .restore = i830_hdmi_restore,
318    .mode_valid = i830_hdmi_mode_valid,
319    .mode_fixup = i830_hdmi_mode_fixup,
320    .prepare = i830_output_prepare,
321    .mode_set = i830_hdmi_mode_set,
322    .commit = i830_output_commit,
323    .detect = i830_hdmi_detect,
324    .get_modes = i830_ddc_get_modes,
325    .set_property = i830_hdmi_set_property,
326    .destroy = i830_hdmi_destroy
327};
328
329void
330i830_hdmi_init(ScrnInfoPtr pScrn, int output_reg)
331{
332    xf86OutputPtr output;
333    I830OutputPrivatePtr intel_output;
334    struct i830_hdmi_priv *dev_priv;
335
336    output = xf86OutputCreate(pScrn, &i830_hdmi_output_funcs,
337			      (output_reg == SDVOB) ? "HDMI-1" : "HDMI-2");
338    if (!output)
339	return;
340    intel_output = xnfcalloc(sizeof (I830OutputPrivateRec) +
341			     sizeof (struct i830_hdmi_priv), 1);
342    if (intel_output == NULL) {
343	xf86OutputDestroy(output);
344	return;
345    }
346    output->driver_private = intel_output;
347    output->interlaceAllowed = FALSE;
348    output->doubleScanAllowed = FALSE;
349
350    dev_priv = (struct i830_hdmi_priv *)(intel_output + 1);
351    dev_priv->output_reg = output_reg;
352    dev_priv->has_hdmi_sink = FALSE;
353
354    intel_output->dev_priv = dev_priv;
355    intel_output->type = I830_OUTPUT_HDMI;
356    intel_output->pipe_mask = ((1 << 0) | (1 << 1));
357    intel_output->clone_mask = (1 << I830_OUTPUT_HDMI);
358
359    /* Set up the DDC bus. */
360    if (output_reg == SDVOB)
361	I830I2CInit(pScrn, &intel_output->pDDCBus, GPIOE, "HDMIDDC_B");
362    else
363	I830I2CInit(pScrn, &intel_output->pDDCBus, GPIOD, "HDMIDDC_C");
364
365    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
366	       "HDMI output %d detected\n",
367	       (output_reg == SDVOB) ? 1 : 2);
368}
369