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