xf86-video-intel/src/i830_display.c

1114 lines
30 KiB
C

/* -*- c-basic-offset: 4 -*- */
/*
* Copyright © 2006 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Authors:
* Eric Anholt <eric@anholt.net>
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "xf86.h"
#include "xf86_ansic.h"
#include "i830.h"
#include "i830_bios.h"
#include "i830_display.h"
#include "i830_debug.h"
#include "i830_xf86Modes.h"
/** Returns the pixel clock for the given refclk and divisors. */
static int i830_clock(int refclk, int m1, int m2, int n, int p1, int p2)
{
return refclk * (5 * m1 + m2) / n / (p1 * p2);
}
static void
i830PrintPll(char *prefix, int refclk, int m1, int m2, int n, int p1, int p2)
{
int dotclock;
dotclock = i830_clock(refclk, m1, m2, n, p1, p2);
ErrorF("%s: dotclock %d ((%d, %d), %d, (%d, %d))\n", prefix, dotclock,
m1, m2, n, p1, p2);
}
/**
* Returns whether the given set of divisors are valid for a given refclk with
* the given outputs.
*
* The equation for these divisors would be:
* clk = refclk * (5 * m1 + m2) / n / (p1 * p2)
*/
static Bool
i830PllIsValid(ScrnInfoPtr pScrn, int outputs, int refclk, int m1, int m2,
int n, int p1, int p2)
{
I830Ptr pI830 = I830PTR(pScrn);
int p, m, vco, dotclock;
int min_m1, max_m1, min_m2, max_m2, min_m, max_m, min_n, max_n;
int min_p1, max_p1, min_p, max_p, min_vco, max_vco, min_dot, max_dot;
if (IS_I9XX(pI830)) {
min_m1 = 10;
max_m1 = 20;
min_m2 = 5;
max_m2 = 9;
min_m = 70;
max_m = 120;
min_n = 3;
max_n = 8;
min_p1 = 1;
max_p1 = 8;
if (outputs & PIPE_LCD_ACTIVE) {
min_p = 7;
max_p = 98;
} else {
min_p = 5;
max_p = 80;
}
min_vco = 1400000;
max_vco = 2800000;
min_dot = 20000;
max_dot = 400000;
} else {
min_m1 = 18;
max_m1 = 26;
min_m2 = 6;
max_m2 = 16;
min_m = 96;
max_m = 140;
min_n = 3;
max_n = 16;
min_p1 = 2;
max_p1 = 18;
min_vco = 930000;
max_vco = 1400000;
min_dot = 20000;
max_dot = 350000;
min_p = 4;
max_p = 128;
}
p = p1 * p2;
m = 5 * m1 + m2;
vco = refclk * m / n;
dotclock = i830_clock(refclk, m1, m2, n, p1, p2);
if (p1 < min_p1 || p1 > max_p1)
return FALSE;
if (p < min_p || p > max_p)
return FALSE;
if (m2 < min_m2 || m2 > max_m2)
return FALSE;
if (m1 < min_m1 || m1 > max_m1)
return FALSE;
if (m1 <= m2)
return FALSE;
if (m < min_m || m > max_m)
return FALSE;
if (n < min_n || n > max_n)
return FALSE;
if (vco < min_vco || vco > max_vco)
return FALSE;
/* XXX: We may need to be checking "Dot clock" depending on the multiplier,
* output, etc., rather than just a single range.
*/
if (dotclock < min_dot || dotclock > max_dot)
return FALSE;
return TRUE;
}
/**
* Returns a set of divisors for the desired target clock with the given refclk,
* or FALSE. Divisor values are the actual divisors for
* clk = refclk * (5 * m1 + m2) / n / (p1 * p2)
*/
static Bool
i830FindBestPLL(ScrnInfoPtr pScrn, int outputs, int target, int refclk,
int *outm1, int *outm2, int *outn, int *outp1, int *outp2)
{
I830Ptr pI830 = I830PTR(pScrn);
int m1, m2, n, p1, p2;
int err = target;
int min_m1, max_m1, min_m2, max_m2, min_n, max_n, min_p1, max_p1;
if (IS_I9XX(pI830)) {
min_m1 = 10;
max_m1 = 20;
min_m2 = 5;
max_m2 = 9;
min_n = 3;
max_n = 8;
min_p1 = 1;
max_p1 = 8;
if (outputs & PIPE_LCD_ACTIVE) {
if (target < 200000) /* XXX: Is this the right cutoff? */
p2 = 14;
else
p2 = 7;
} else {
if (target < 200000)
p2 = 10;
else
p2 = 5;
}
} else {
min_m1 = 18;
max_m1 = 26;
min_m2 = 6;
max_m2 = 16;
min_n = 3;
max_n = 16;
min_p1 = 2;
max_p1 = 18;
if (target < 165000)
p2 = 4;
else
p2 = 2;
}
for (m1 = min_m1; m1 <= max_m1; m1++) {
for (m2 = min_m2; m2 < max_m2; m2++) {
for (n = min_n; n <= max_n; n++) {
for (p1 = min_p1; p1 <= max_p1; p1++) {
int clock, this_err;
if (!i830PllIsValid(pScrn, outputs, refclk, m1, m2, n,
p1, p2)) {
continue;
}
clock = i830_clock(refclk, m1, m2, n, p1, p2);
this_err = abs(clock - target);
if (this_err < err) {
*outm1 = m1;
*outm2 = m2;
*outn = n;
*outp1 = p1;
*outp2 = p2;
err = this_err;
}
}
}
}
}
return (err != target);
}
static void
i830WaitForVblank(ScrnInfoPtr pScreen)
{
/* Wait for 20ms, i.e. one cycle at 50hz. */
usleep(20000);
}
void
i830PipeSetBase(ScrnInfoPtr pScrn, int pipe, int x, int y)
{
I830Ptr pI830 = I830PTR(pScrn);
unsigned long Start;
if (I830IsPrimary(pScrn))
Start = pI830->FrontBuffer.Start;
else {
I830Ptr pI8301 = I830PTR(pI830->entityPrivate->pScrn_1);
Start = pI8301->FrontBuffer2.Start;
}
if (pipe == 0)
OUTREG(DSPABASE, Start + ((y * pScrn->displayWidth + x) * pI830->cpp));
else
OUTREG(DSPBBASE, Start + ((y * pScrn->displayWidth + x) * pI830->cpp));
}
/**
* Sets the given video mode on the given pipe. Assumes that plane A feeds
* pipe A, and plane B feeds pipe B. Should not affect the other planes/pipes.
*/
static Bool
i830PipeSetMode(ScrnInfoPtr pScrn, DisplayModePtr pMode, int pipe)
{
I830Ptr pI830 = I830PTR(pScrn);
int m1 = 0, m2 = 0, n = 0, p1 = 0, p2 = 0;
CARD32 dpll = 0, fp = 0, temp;
CARD32 htot, hblank, hsync, vtot, vblank, vsync, dspcntr;
CARD32 pipesrc, dspsize, adpa;
CARD32 sdvob = 0, sdvoc = 0, dvo = 0;
Bool ok, is_sdvo, is_dvo;
int refclk, pixel_clock, sdvo_pixel_multiply;
int outputs;
DisplayModePtr pMasterMode = pMode;
assert(pMode->VRefresh != 0.0);
/* If we've got a list of modes probed for the device, find the best match
* in there to the requested mode.
*/
if (pI830->pipeMon[pipe] != NULL) {
DisplayModePtr pBest = NULL, pScan;
assert(pScan->VRefresh != 0.0);
for (pScan = pI830->pipeMon[pipe]->Modes; pScan != NULL;
pScan = pScan->next)
{
/* If there's an exact match, we're done. */
if (I830ModesEqual(pScan, pMode)) {
pBest = pMode;
break;
}
/* Reject if it's larger than the desired mode. */
if (pScan->HDisplay > pMode->HDisplay ||
pScan->VDisplay > pMode->VDisplay)
{
continue;
}
if (pBest == NULL) {
pBest = pScan;
continue;
}
/* Find if it's closer to the right size than the current best
* option.
*/
if ((pScan->HDisplay > pBest->HDisplay &&
pScan->VDisplay >= pBest->VDisplay) ||
(pScan->HDisplay >= pBest->HDisplay &&
pScan->VDisplay > pBest->VDisplay))
{
pBest = pScan;
continue;
}
/* Find if it's still closer to the right refresh than the current
* best resolution.
*/
if (pScan->HDisplay == pBest->HDisplay &&
pScan->VDisplay == pBest->VDisplay &&
(fabs(pScan->VRefresh - pMode->VRefresh) <
fabs(pBest->VRefresh - pMode->VRefresh)))
{
pBest = pScan;
}
}
if (pBest != NULL) {
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
"Choosing pipe %d's mode %dx%d@%.1f instead of xf86 "
"mode %dx%d@%.1f\n", pipe,
pBest->HDisplay, pBest->VDisplay, pBest->VRefresh,
pMode->HDisplay, pMode->VDisplay, pMode->VRefresh);
pMode = pBest;
}
}
if (pipe == 0)
outputs = pI830->operatingDevices & 0xff;
else
outputs = (pI830->operatingDevices >> 8) & 0xff;
if (outputs & PIPE_LCD_ACTIVE) {
if (I830ModesEqual(&pI830->pipeCurMode[pipe], pMasterMode))
return TRUE;
} else {
if (I830ModesEqual(&pI830->pipeCurMode[pipe], pMode))
return TRUE;
}
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Requested pix clock: %d\n",
pMode->Clock);
if ((outputs & PIPE_LCD_ACTIVE) && (outputs & ~PIPE_LCD_ACTIVE)) {
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
"Can't enable LVDS and non-LVDS on the same pipe\n");
return FALSE;
}
if (((outputs & PIPE_TV_ACTIVE) && (outputs & ~PIPE_TV_ACTIVE)) ||
((outputs & PIPE_TV2_ACTIVE) && (outputs & ~PIPE_TV2_ACTIVE))) {
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
"Can't enable a TV and any other output on the same pipe\n");
return FALSE;
}
if (pipe == 0 && (outputs & PIPE_LCD_ACTIVE)) {
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
"Can't support LVDS on pipe A\n");
return FALSE;
}
if ((outputs & PIPE_DFP_ACTIVE) || (outputs & PIPE_DFP2_ACTIVE)) {
/* We'll change how we control outputs soon, but to get the SDVO code up
* and running, just check for these two possibilities.
*/
if (IS_I9XX(pI830)) {
is_sdvo = TRUE;
is_dvo = FALSE;
} else {
is_dvo = TRUE;
is_sdvo = FALSE;
}
} else {
is_sdvo = FALSE;
is_dvo = FALSE;
}
htot = (pMode->CrtcHDisplay - 1) | ((pMode->CrtcHTotal - 1) << 16);
hblank = (pMode->CrtcHBlankStart - 1) | ((pMode->CrtcHBlankEnd - 1) << 16);
hsync = (pMode->CrtcHSyncStart - 1) | ((pMode->CrtcHSyncEnd - 1) << 16);
vtot = (pMode->CrtcVDisplay - 1) | ((pMode->CrtcVTotal - 1) << 16);
vblank = (pMode->CrtcVBlankStart - 1) | ((pMode->CrtcVBlankEnd - 1) << 16);
vsync = (pMode->CrtcVSyncStart - 1) | ((pMode->CrtcVSyncEnd - 1) << 16);
pipesrc = ((pMode->HDisplay - 1) << 16) | (pMode->VDisplay - 1);
dspsize = ((pMode->VDisplay - 1) << 16) | (pMode->HDisplay - 1);
pixel_clock = pMode->Clock;
if (outputs & PIPE_LCD_ACTIVE && pI830->panel_fixed_hactive != 0)
{
/* To enable panel fitting, we need to set the pipe timings to that of
* the screen at its full resolution. So, drop the timings from the
* BIOS VBT tables here.
*/
htot = (pI830->panel_fixed_hactive - 1) |
((pI830->panel_fixed_hactive + pI830->panel_fixed_hblank - 1)
<< 16);
hblank = (pI830->panel_fixed_hactive - 1) |
((pI830->panel_fixed_hactive + pI830->panel_fixed_hblank - 1)
<< 16);
hsync = (pI830->panel_fixed_hactive + pI830->panel_fixed_hsyncoff - 1) |
((pI830->panel_fixed_hactive + pI830->panel_fixed_hsyncoff +
pI830->panel_fixed_hsyncwidth - 1) << 16);
vtot = (pI830->panel_fixed_vactive - 1) |
((pI830->panel_fixed_vactive + pI830->panel_fixed_vblank - 1)
<< 16);
vblank = (pI830->panel_fixed_vactive - 1) |
((pI830->panel_fixed_vactive + pI830->panel_fixed_vblank - 1)
<< 16);
vsync = (pI830->panel_fixed_vactive + pI830->panel_fixed_vsyncoff - 1) |
((pI830->panel_fixed_vactive + pI830->panel_fixed_vsyncoff +
pI830->panel_fixed_vsyncwidth - 1) << 16);
pixel_clock = pI830->panel_fixed_clock;
if (pMasterMode->HDisplay <= pI830->panel_fixed_hactive &&
pMasterMode->HDisplay <= pI830->panel_fixed_vactive)
{
pipesrc = ((pMasterMode->HDisplay - 1) << 16) |
(pMasterMode->VDisplay - 1);
dspsize = ((pMasterMode->VDisplay - 1) << 16) |
(pMasterMode->HDisplay - 1);
}
}
if (pMode->Clock >= 100000)
sdvo_pixel_multiply = 1;
else if (pMode->Clock >= 50000)
sdvo_pixel_multiply = 2;
else
sdvo_pixel_multiply = 4;
/* In SDVO, we need to keep the clock on the bus between 1Ghz and 2Ghz.
* The clock on the bus is 10 times the pixel clock normally. If that
* would be too low, we run the DPLL at a multiple of the pixel clock, and
* tell the SDVO device the multiplier so it can throw away the dummy bytes.
*/
if (is_sdvo) {
pixel_clock *= sdvo_pixel_multiply;
}
if (IS_I9XX(pI830)) {
refclk = 96000;
} else {
refclk = 48000;
}
ok = i830FindBestPLL(pScrn, outputs, pixel_clock, refclk, &m1, &m2, &n,
&p1, &p2);
if (!ok) {
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
"Couldn't find PLL settings for mode!\n");
return FALSE;
}
dpll = DPLL_VCO_ENABLE | DPLL_VGA_MODE_DIS;
if (IS_I9XX(pI830)) {
if (outputs & PIPE_LCD_ACTIVE)
dpll |= DPLLB_MODE_LVDS;
else
dpll |= DPLLB_MODE_DAC_SERIAL;
dpll |= (1 << (p1 - 1)) << 16;
switch (p2) {
case 5:
dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_5;
break;
case 7:
dpll |= DPLLB_LVDS_P2_CLOCK_DIV_7;
break;
case 10:
dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_10;
break;
case 14:
dpll |= DPLLB_LVDS_P2_CLOCK_DIV_14;
break;
}
} else {
dpll |= (p1 - 2) << 16;
if (p2 == 4)
dpll |= PLL_P2_DIVIDE_BY_4;
}
if (outputs & (PIPE_TV_ACTIVE | PIPE_TV2_ACTIVE))
dpll |= PLL_REF_INPUT_TVCLKINBC;
else
dpll |= PLL_REF_INPUT_DREFCLK;
if (is_dvo) {
dpll |= DPLL_DVO_HIGH_SPEED;
/* Save the data order, since I don't know what it should be set to. */
dvo = INREG(DVOC) & (DVO_PRESERVE_MASK | DVO_DATA_ORDER_GBRG);
dvo |= DVO_ENABLE;
dvo |= DVO_DATA_ORDER_FP | DVO_BORDER_ENABLE | DVO_BLANK_ACTIVE_HIGH;
if (pipe == 1)
dvo |= DVO_PIPE_B_SELECT;
if (pMode->Flags & V_PHSYNC)
dvo |= DVO_HSYNC_ACTIVE_HIGH;
if (pMode->Flags & V_PVSYNC)
dvo |= DVO_VSYNC_ACTIVE_HIGH;
OUTREG(DVOC, dvo & ~DVO_ENABLE);
}
if (is_sdvo) {
dpll |= DPLL_DVO_HIGH_SPEED;
ErrorF("DVOB: %08x\nDVOC: %08x\n", (int)INREG(SDVOB), (int)INREG(SDVOC));
sdvob = INREG(SDVOB) & SDVOB_PRESERVE_MASK;
sdvoc = INREG(SDVOC) & SDVOC_PRESERVE_MASK;
sdvob |= SDVO_ENABLE | (9 << 19) | SDVO_BORDER_ENABLE;
sdvoc |= 9 << 19;
if (pipe == 1)
sdvob |= SDVO_PIPE_B_SELECT;
if (IS_I945G(pI830) || IS_I945GM(pI830))
dpll |= (sdvo_pixel_multiply - 1) << SDVO_MULTIPLIER_SHIFT_HIRES;
else
sdvob |= (sdvo_pixel_multiply - 1) << SDVO_PORT_MULTIPLY_SHIFT;
OUTREG(SDVOC, INREG(SDVOC) & ~SDVO_ENABLE);
OUTREG(SDVOB, INREG(SDVOB) & ~SDVO_ENABLE);
}
fp = ((n - 2) << 16) | ((m1 - 2) << 8) | (m2 - 2);
#if 1
ErrorF("hact: %d htot: %d hbstart: %d hbend: %d hsyncstart: %d hsyncend: %d\n",
(int)(htot & 0xffff) + 1, (int)(htot >> 16) + 1,
(int)(hblank & 0xffff) + 1, (int)(hblank >> 16) + 1,
(int)(hsync & 0xffff) + 1, (int)(hsync >> 16) + 1);
ErrorF("vact: %d vtot: %d vbstart: %d vbend: %d vsyncstart: %d vsyncend: %d\n",
(int)(vtot & 0xffff) + 1, (int)(vtot >> 16) + 1,
(int)(vblank & 0xffff) + 1, (int)(vblank >> 16) + 1,
(int)(vsync & 0xffff) + 1, (int)(vsync >> 16) + 1);
ErrorF("pipesrc: %dx%d, dspsize: %dx%d\n",
(int)(pipesrc >> 16) + 1, (int)(pipesrc & 0xffff) + 1,
(int)(dspsize & 0xffff) + 1, (int)(dspsize >> 16) + 1);
#endif
i830PrintPll("chosen", refclk, m1, m2, n, p1, p2);
ErrorF("clock regs: 0x%08x, 0x%08x\n", (int)dpll, (int)fp);
dspcntr = DISPLAY_PLANE_ENABLE;
switch (pScrn->bitsPerPixel) {
case 8:
dspcntr |= DISPPLANE_8BPP | DISPPLANE_GAMMA_ENABLE;
break;
case 16:
if (pScrn->depth == 15)
dspcntr |= DISPPLANE_15_16BPP;
else
dspcntr |= DISPPLANE_16BPP;
break;
case 32:
dspcntr |= DISPPLANE_32BPP_NO_ALPHA;
break;
default:
FatalError("unknown display bpp\n");
}
if (is_sdvo)
adpa = ADPA_DAC_DISABLE;
else
adpa = ADPA_DAC_ENABLE;
if (pMode->Flags & V_PHSYNC)
adpa |= ADPA_HSYNC_ACTIVE_HIGH;
if (pMode->Flags & V_PVSYNC)
adpa |= ADPA_VSYNC_ACTIVE_HIGH;
if (pipe == 0) {
dspcntr |= DISPPLANE_SEL_PIPE_A;
adpa |= ADPA_PIPE_A_SELECT;
} else {
dspcntr |= DISPPLANE_SEL_PIPE_B;
adpa |= ADPA_PIPE_B_SELECT;
}
OUTREG(VGACNTRL, VGA_DISP_DISABLE);
/* Set up display timings and PLLs for the pipe. */
if (pipe == 0) {
/* First, disable display planes */
temp = INREG(DSPACNTR);
OUTREG(DSPACNTR, temp & ~DISPLAY_PLANE_ENABLE);
/* Wait for vblank for the disable to take effect */
i830WaitForVblank(pScrn);
/* Next, disable display pipes */
temp = INREG(PIPEACONF);
OUTREG(PIPEACONF, temp & ~PIPEACONF_ENABLE);
OUTREG(FPA0, fp);
OUTREG(DPLL_A, dpll);
OUTREG(HTOTAL_A, htot);
OUTREG(HBLANK_A, hblank);
OUTREG(HSYNC_A, hsync);
OUTREG(VTOTAL_A, vtot);
OUTREG(VBLANK_A, vblank);
OUTREG(VSYNC_A, vsync);
OUTREG(DSPASTRIDE, pScrn->displayWidth * pI830->cpp);
OUTREG(DSPASIZE, dspsize);
OUTREG(DSPAPOS, 0);
i830PipeSetBase(pScrn, pipe, pScrn->frameX0, pScrn->frameY0);
OUTREG(PIPEASRC, pipesrc);
/* Then, turn the pipe on first */
temp = INREG(PIPEACONF);
OUTREG(PIPEACONF, temp | PIPEACONF_ENABLE);
/* And then turn the plane on */
OUTREG(DSPACNTR, dspcntr);
} else {
/* Always make sure the LVDS is off before we play with DPLLs and pipe
* configuration.
*/
i830SetLVDSPanelPower(pScrn, FALSE);
/* First, disable display planes */
temp = INREG(DSPBCNTR);
OUTREG(DSPBCNTR, temp & ~DISPLAY_PLANE_ENABLE);
/* Wait for vblank for the disable to take effect */
i830WaitForVblank(pScrn);
/* Next, disable display pipes */
temp = INREG(PIPEBCONF);
OUTREG(PIPEBCONF, temp & ~PIPEBCONF_ENABLE);
if (outputs & PIPE_LCD_ACTIVE) {
/* Disable the PLL before messing with LVDS enable */
OUTREG(FPB0, fp & ~DPLL_VCO_ENABLE);
/* LVDS must be powered on before PLL is enabled and before power
* sequencing the panel.
*/
temp = INREG(LVDS);
OUTREG(LVDS, temp | LVDS_PORT_EN | LVDS_PIPEB_SELECT);
}
OUTREG(FPB0, fp);
OUTREG(DPLL_B, dpll);
OUTREG(HTOTAL_B, htot);
OUTREG(HBLANK_B, hblank);
OUTREG(HSYNC_B, hsync);
OUTREG(VTOTAL_B, vtot);
OUTREG(VBLANK_B, vblank);
OUTREG(VSYNC_B, vsync);
OUTREG(DSPBSTRIDE, pScrn->displayWidth * pI830->cpp);
OUTREG(DSPBSIZE, dspsize);
OUTREG(DSPBPOS, 0);
i830PipeSetBase(pScrn, pipe, pScrn->frameX0, pScrn->frameY0);
OUTREG(PIPEBSRC, pipesrc);
if (outputs & PIPE_LCD_ACTIVE) {
CARD32 pfit_control;
/* Enable automatic panel scaling so that non-native modes fill the
* screen.
*/
/* XXX: Allow (auto-?) enabling of 8-to-6 dithering */
pfit_control = (PFIT_ENABLE |
VERT_AUTO_SCALE | HORIZ_AUTO_SCALE |
VERT_INTERP_BILINEAR | HORIZ_INTERP_BILINEAR);
if (pI830->panel_wants_dither)
pfit_control |= PANEL_8TO6_DITHER_ENABLE;
OUTREG(PFIT_CONTROL, pfit_control);
}
/* Then, turn the pipe on first */
temp = INREG(PIPEBCONF);
OUTREG(PIPEBCONF, temp | PIPEBCONF_ENABLE);
/* And then turn the plane on */
OUTREG(DSPBCNTR, dspcntr);
if (outputs & PIPE_LCD_ACTIVE) {
i830SetLVDSPanelPower(pScrn, TRUE);
}
}
if (outputs & PIPE_CRT_ACTIVE)
OUTREG(ADPA, adpa);
if (is_dvo) {
/*OUTREG(DVOB_SRCDIM, (pMode->HDisplay << DVO_SRCDIM_HORIZONTAL_SHIFT) |
(pMode->VDisplay << DVO_SRCDIM_VERTICAL_SHIFT));*/
OUTREG(DVOC_SRCDIM, (pMode->HDisplay << DVO_SRCDIM_HORIZONTAL_SHIFT) |
(pMode->VDisplay << DVO_SRCDIM_VERTICAL_SHIFT));
/*OUTREG(DVOB, dvo);*/
OUTREG(DVOC, dvo);
}
if (is_sdvo) {
OUTREG(SDVOB, sdvob);
OUTREG(SDVOC, sdvoc);
}
if (outputs & PIPE_LCD_ACTIVE) {
pI830->pipeCurMode[pipe] = *pMasterMode;
} else {
pI830->pipeCurMode[pipe] = *pMode;
}
return TRUE;
}
static void
i830DisableUnusedFunctions(ScrnInfoPtr pScrn)
{
I830Ptr pI830 = I830PTR(pScrn);
int outputsA, outputsB;
outputsA = pI830->operatingDevices & 0xff;
outputsB = (pI830->operatingDevices >> 8) & 0xff;
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling unused functions\n");
/* First, disable the unused outputs */
if ((outputsA & PIPE_CRT_ACTIVE) == 0 &&
(outputsB & PIPE_CRT_ACTIVE) == 0)
{
CARD32 adpa = INREG(ADPA);
if (adpa & ADPA_DAC_ENABLE) {
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling CRT output\n");
OUTREG(ADPA, adpa & ~ADPA_DAC_ENABLE);
}
}
if ((outputsB & PIPE_LCD_ACTIVE) == 0) {
CARD32 pp_status = INREG(PP_STATUS);
if (pp_status & PP_ON) {
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling LVDS output\n");
i830SetLVDSPanelPower(pScrn, FALSE);
}
}
if (IS_I9XX(pI830) && ((outputsA & PIPE_DFP_ACTIVE) == 0 &&
(outputsB & PIPE_DFP_ACTIVE) == 0))
{
CARD32 sdvob = INREG(SDVOB);
CARD32 sdvoc = INREG(SDVOC);
if (sdvob & SDVO_ENABLE) {
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling SDVOB output\n");
OUTREG(SDVOB, sdvob & ~SDVO_ENABLE);
}
if (sdvoc & SDVO_ENABLE) {
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling SDVOC output\n");
OUTREG(SDVOC, sdvoc & ~SDVO_ENABLE);
}
}
/* Now, any unused plane, pipe, and DPLL (FIXME: except for DVO, i915
* internal TV) should have no outputs trying to pull data out of it, so
* we're ready to turn those off.
*/
if (!pI830->planeEnabled[0]) {
CARD32 dspcntr, pipeconf, dpll;
dspcntr = INREG(DSPACNTR);
if (dspcntr & DISPLAY_PLANE_ENABLE) {
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling plane A\n");
OUTREG(DSPACNTR, dspcntr & ~DISPLAY_PLANE_ENABLE);
/* Wait for vblank for the disable to take effect */
i830WaitForVblank(pScrn);
}
pipeconf = INREG(PIPEACONF);
if (pipeconf & PIPEACONF_ENABLE) {
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling pipe A\n");
OUTREG(PIPEACONF, pipeconf & ~PIPEACONF_ENABLE);
}
dpll = INREG(DPLL_A);
if (dpll & DPLL_VCO_ENABLE) {
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling DPLL A\n");
OUTREG(DPLL_A, dpll & ~DPLL_VCO_ENABLE);
}
memset(&pI830->pipeCurMode[0], 0, sizeof(pI830->pipeCurMode[0]));
}
if (!pI830->planeEnabled[1]) {
CARD32 dspcntr, pipeconf, dpll;
dspcntr = INREG(DSPBCNTR);
if (dspcntr & DISPLAY_PLANE_ENABLE) {
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling plane B\n");
OUTREG(DSPBCNTR, dspcntr & ~DISPLAY_PLANE_ENABLE);
/* Wait for vblank for the disable to take effect */
i830WaitForVblank(pScrn);
}
pipeconf = INREG(PIPEBCONF);
if (pipeconf & PIPEBCONF_ENABLE) {
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling pipe B\n");
OUTREG(PIPEBCONF, pipeconf & ~PIPEBCONF_ENABLE);
}
dpll = INREG(DPLL_B);
if (dpll & DPLL_VCO_ENABLE) {
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling DPLL B\n");
OUTREG(DPLL_B, dpll & ~DPLL_VCO_ENABLE);
}
memset(&pI830->pipeCurMode[1], 0, sizeof(pI830->pipeCurMode[1]));
}
}
/**
* This function sets the given mode on the active pipes.
*/
Bool
i830SetMode(ScrnInfoPtr pScrn, DisplayModePtr pMode)
{
I830Ptr pI830 = I830PTR(pScrn);
Bool ok = TRUE;
CARD32 planeA, planeB;
#ifdef XF86DRI
Bool didLock = FALSE;
#endif
int i;
DPRINTF(PFX, "i830SetMode\n");
#ifdef XF86DRI
didLock = I830DRILock(pScrn);
#endif
if (pI830->operatingDevices & 0xff) {
pI830->planeEnabled[0] = 1;
} else {
pI830->planeEnabled[0] = 0;
}
if (pI830->operatingDevices & 0xff00) {
pI830->planeEnabled[1] = 1;
} else {
pI830->planeEnabled[1] = 0;
}
for (i = 0; i < pI830->num_outputs; i++) {
struct _I830OutputRec *output = &pI830->output[i];
if (output->sdvo_drv)
I830SDVOPreSetMode(output->sdvo_drv, pMode);
if (output->i2c_drv != NULL)
output->i2c_drv->vid_rec->Mode(output->i2c_drv->dev_priv,
pMode);
}
if (pI830->planeEnabled[0]) {
ok = i830PipeSetMode(pScrn, pMode, 0);
if (!ok)
goto done;
}
if (pI830->planeEnabled[1]) {
ok = i830PipeSetMode(pScrn, pMode, 1);
if (!ok)
goto done;
}
for (i = 0; i < pI830->num_outputs; i++) {
if (pI830->output[i].sdvo_drv)
I830SDVOPostSetMode(pI830->output[i].sdvo_drv, pMode);
}
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Mode bandwidth is %d Mpixel/s\n",
(int)(pMode->HDisplay * pMode->VDisplay *
pMode->VRefresh / 1000000));
if (pI830->savedCurrentMode) {
/* We're done with the currentMode that the last randr probe had left
* behind, so free it.
*/
xfree(pI830->savedCurrentMode->name);
xfree(pI830->savedCurrentMode);
pI830->savedCurrentMode = NULL;
/* If we might have enabled/disabled some pipes, we need to reset
* cloning mode support.
*/
if ((pI830->operatingDevices & 0x00ff) &&
(pI830->operatingDevices & 0xff00))
{
pI830->Clone = TRUE;
} else {
pI830->Clone = FALSE;
}
/* If HW cursor currently showing, reset cursor state */
if (pI830->CursorInfoRec && !pI830->SWCursor && pI830->cursorOn)
pI830->CursorInfoRec->ShowCursor(pScrn);
}
i830DisableUnusedFunctions(pScrn);
planeA = INREG(DSPACNTR);
planeB = INREG(DSPBCNTR);
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
"Display plane A is now %s and connected to %s.\n",
pI830->planeEnabled[0] ? "enabled" : "disabled",
planeA & DISPPLANE_SEL_PIPE_MASK ? "Pipe B" : "Pipe A");
if (pI830->availablePipes == 2)
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
"Display plane B is now %s and connected to %s.\n",
pI830->planeEnabled[1] ? "enabled" : "disabled",
planeB & DISPPLANE_SEL_PIPE_MASK ? "Pipe B" : "Pipe A");
#ifdef XF86DRI
I830DRISetVBlankInterrupt (pScrn, TRUE);
#endif
done:
#ifdef XF86DRI
if (didLock)
I830DRIUnlock(pScrn);
#endif
i830DumpRegs (pScrn);
I830DumpSDVO (pScrn);
return ok;
}
/**
* Uses CRT_HOTPLUG_EN and CRT_HOTPLUG_STAT to detect CRT presence.
*
* Only for I945G/GM.
*/
static Bool
i830HotplugDetectCRT(ScrnInfoPtr pScrn)
{
I830Ptr pI830 = I830PTR(pScrn);
CARD32 temp;
const int timeout_ms = 1000;
int starttime, curtime;
temp = INREG(PORT_HOTPLUG_EN);
OUTREG(PORT_HOTPLUG_EN, temp | CRT_HOTPLUG_FORCE_DETECT | (1 << 5));
for (curtime = starttime = GetTimeInMillis();
(curtime - starttime) < timeout_ms; curtime = GetTimeInMillis())
{
if ((INREG(PORT_HOTPLUG_EN) & CRT_HOTPLUG_FORCE_DETECT) == 0)
break;
}
if ((INREG(PORT_HOTPLUG_STAT) & CRT_HOTPLUG_MONITOR_MASK) ==
CRT_HOTPLUG_MONITOR_COLOR)
{
return TRUE;
} else {
return FALSE;
}
}
/**
* Detects CRT presence by checking for load.
*
* Requires that the current pipe's DPLL is active. This will cause flicker
* on the CRT, so it should not be used while the display is being used. Only
* color (not monochrome) displays are detected.
*/
static Bool
i830LoadDetectCRT(ScrnInfoPtr pScrn)
{
I830Ptr pI830 = I830PTR(pScrn);
CARD32 adpa, pipeconf;
CARD8 st00;
int pipeconf_reg, bclrpat_reg, dpll_reg;
int pipe;
pipe = pI830->pipe;
if (pipe == 0) {
bclrpat_reg = BCLRPAT_A;
pipeconf_reg = PIPEACONF;
dpll_reg = DPLL_A;
} else {
bclrpat_reg = BCLRPAT_B;
pipeconf_reg = PIPEBCONF;
dpll_reg = DPLL_B;
}
/* Don't try this if the DPLL isn't running. */
if (!(INREG(dpll_reg) & DPLL_VCO_ENABLE))
return FALSE;
adpa = INREG(ADPA);
/* Enable CRT output if disabled. */
if (!(adpa & ADPA_DAC_ENABLE)) {
OUTREG(ADPA, adpa | ADPA_DAC_ENABLE |
((pipe == 1) ? ADPA_PIPE_B_SELECT : 0));
}
/* Set the border color to red, green. Maybe we should save/restore this
* reg.
*/
OUTREG(bclrpat_reg, 0x00500050);
/* Force the border color through the active region */
pipeconf = INREG(pipeconf_reg);
OUTREG(pipeconf_reg, pipeconf | PIPECONF_FORCE_BORDER);
/* Read the ST00 VGA status register */
st00 = pI830->readStandard(pI830, 0x3c2);
/* Restore previous settings */
OUTREG(pipeconf_reg, pipeconf);
OUTREG(ADPA, adpa);
if (st00 & (1 << 4))
return TRUE;
else
return FALSE;
}
/**
* Detects CRT presence by probing for a response on the DDC address.
*
* This takes approximately 5ms in testing on an i915GM, with CRT connected or
* not.
*/
static Bool
i830DDCDetectCRT(ScrnInfoPtr pScrn)
{
I830Ptr pI830 = I830PTR(pScrn);
struct _I830OutputRec *output;
output = &pI830->output[0];
/* CRT should always be at 0, but check anyway */
if (output->type != I830_OUTPUT_ANALOG)
return FALSE;
return xf86I2CProbeAddress(output->pDDCBus, 0x00A0);
}
/**
* Attempts to detect CRT presence through any method available.
*
* @param allow_disturb enables detection methods that may cause flickering
* on active displays.
*/
Bool
i830DetectCRT(ScrnInfoPtr pScrn, Bool allow_disturb)
{
I830Ptr pI830 = I830PTR(pScrn);
Bool found_ddc;
if (IS_I945G(pI830) || IS_I945GM(pI830))
return i830HotplugDetectCRT(pScrn);
found_ddc = i830DDCDetectCRT(pScrn);
if (found_ddc)
return TRUE;
/* Use the load-detect method if we're not currently outputting to the CRT,
* or we don't care.
*
* Actually, the method is unreliable currently. We need to not share a
* pipe, as it seems having other outputs on that pipe will result in a
* false positive.
*/
if (0 && (allow_disturb || !(INREG(ADPA) & !ADPA_DAC_ENABLE))) {
return i830LoadDetectCRT(pScrn);
}
return FALSE;
}
/**
* Sets the power state for the panel.
*/
void
i830SetLVDSPanelPower(ScrnInfoPtr pScrn, Bool on)
{
I830Ptr pI830 = I830PTR(pScrn);
CARD32 pp_status, pp_control;
CARD32 blc_pwm_ctl;
int backlight_duty_cycle;
blc_pwm_ctl = INREG (BLC_PWM_CTL);
backlight_duty_cycle = blc_pwm_ctl & BACKLIGHT_DUTY_CYCLE_MASK;
if (backlight_duty_cycle)
pI830->backlight_duty_cycle = backlight_duty_cycle;
if (on) {
OUTREG(PP_STATUS, INREG(PP_STATUS) | PP_ON);
OUTREG(PP_CONTROL, INREG(PP_CONTROL) | POWER_TARGET_ON);
do {
pp_status = INREG(PP_STATUS);
pp_control = INREG(PP_CONTROL);
} while (!(pp_status & PP_ON) && !(pp_control & POWER_TARGET_ON));
OUTREG(BLC_PWM_CTL,
(blc_pwm_ctl & ~BACKLIGHT_DUTY_CYCLE_MASK) |
pI830->backlight_duty_cycle);
} else {
OUTREG(BLC_PWM_CTL,
(blc_pwm_ctl & ~BACKLIGHT_DUTY_CYCLE_MASK));
OUTREG(PP_STATUS, INREG(PP_STATUS) & ~PP_ON);
OUTREG(PP_CONTROL, INREG(PP_CONTROL) & ~POWER_TARGET_ON);
do {
pp_status = INREG(PP_STATUS);
pp_control = INREG(PP_CONTROL);
} while ((pp_status & PP_ON) || (pp_control & POWER_TARGET_ON));
}
}