intel_extreme: use the panel fitter for generation 4 devices

LVDS panels must really be driven at their native resolution, otherwise
they will simply not work. This means we should basically never touch
the video timings on that side. We need to only set the source size in
the pipe configuration, and let the panel fitter figure out the scaling.

On my G45 laptop, this allows me to use non-native resolutions on the
laptop display. This also means when booting with a VGA display
connected, I do get a valid display on the internal panel (using the VGA
resolution). VGA still gets "out of range", so we're still not setting
up something there.

If I switch to VGA display in the BIOS, I get a working picture there
and garbage on the internal display, which is progress (before I would
get a black screen on the internal display)

Fixes #12723.
This commit is contained in:
Adrien Destugues 2020-01-04 20:44:48 +01:00
parent d927a11fff
commit abcbfac601
4 changed files with 72 additions and 126 deletions

View File

@ -993,7 +993,11 @@ struct intel_free_graphics_memory {
#define PCH_PANEL_FITTER_H_SCALE 0x90
#define PANEL_FITTER_ENABLED (1 << 31)
#define PANEL_FITTER_FILTER_MASK (3 << 23)
#define PANEL_FITTER_PIPE_MASK (3 << 29)
#define PANEL_FITTER_PIPE_A (0 << 29)
#define PANEL_FITTER_PIPE_B (1 << 29)
#define PANEL_FITTER_SCALING_MODE_MASK (7 << 26)
#define PANEL_FITTER_FILTER_MASK (3 << 24)
struct overlay_scale {
uint32 _reserved0 : 3;

View File

@ -171,7 +171,7 @@ Pipe::_ConfigureTranscoder(display_mode* target)
void
Pipe::ConfigureTimings(display_mode* target)
Pipe::ConfigureTimings(display_mode* target, bool hardware)
{
CALLED();
@ -185,7 +185,7 @@ Pipe::ConfigureTimings(display_mode* target)
/* If there is a transcoder, leave the display at its native resolution,
* and configure only the transcoder (panel fitting will match them
* together). */
if (!fHasTranscoder)
if (!fHasTranscoder && hardware)
{
// update timing (fPipeOffset bumps the DISPLAY_A to B when needed)
write32(INTEL_DISPLAY_A_HTOTAL + fPipeOffset,

View File

@ -39,7 +39,8 @@ public:
void Disable();
void Configure(display_mode* mode);
void ConfigureTimings(display_mode* mode);
void ConfigureTimings(display_mode* mode,
bool hardware = true);
void ConfigureClocks(
const pll_divisors& divisors,
uint32 pixelClock,

View File

@ -468,12 +468,18 @@ LVDSPort::SetDisplayMode(display_mode* target, uint32 colorMode)
panelStatus = PCH_PANEL_STATUS;
}
// Power off Panel
write32(panelControl, read32(panelControl) & ~PANEL_CONTROL_POWER_TARGET_ON);
read32(panelControl);
if (gInfo->shared_info->device_type.Generation() != 4) {
// TODO not needed on any generation if we are using the panel fitter
// Power off Panel
write32(panelControl,
read32(panelControl) & ~PANEL_CONTROL_POWER_TARGET_ON);
read32(panelControl);
if (!wait_for_clear(panelStatus, PANEL_STATUS_POWER_ON, 1000))
ERROR("%s: %s didn't power off within 1000ms!\n", __func__, PortName());
if (!wait_for_clear(panelStatus, PANEL_STATUS_POWER_ON, 1000)) {
ERROR("%s: %s didn't power off within 1000ms!\n", __func__,
PortName());
}
}
#if 0
// Disabled for now as our code doesn't work. Let's hope VESA/EFI has
@ -484,52 +490,40 @@ LVDSPort::SetDisplayMode(display_mode* target, uint32 colorMode)
link->Train(target);
#endif
#if 0
// Disable PanelFitter for now
addr_t panelFitterControl = PCH_PANEL_FITTER_BASE_REGISTER
+ PCH_PANEL_FITTER_CONTROL;
if (fPipe->Index() == INTEL_PIPE_B)
panelFitterControl += PCH_PANEL_FITTER_PIPE_OFFSET;
write32(panelFitterControl, (read32(panelFitterControl) & ~PANEL_FITTER_ENABLED));
read32(panelFitterControl);
#endif
// For LVDS panels, we actually always set the native mode in hardware
// Then we use the panel fitter to scale the picture to that.
display_mode hardwareTarget;
bool needsScaling = false;
// Try to get the panel preferred screen mode from EDID info
#if 0
if (gInfo->shared_info->got_vbt) {
// TODO needed for the other generations as well, in some way?
if (gInfo->shared_info->device_type.Generation() == 4
&& gInfo->shared_info->got_vbt) {
// Set vbios hardware panel mode as base
memcpy(&hardwareTarget, &gInfo->shared_info->panel_mode,
sizeof(display_mode));
hardwareTarget.space = target->space;
if ((hardwareTarget.virtual_width <= target->virtual_width
&& hardwareTarget.virtual_height <= target->virtual_height
&& hardwareTarget.space <= target->space)
|| intel_propose_display_mode(&hardwareTarget, target, target)) {
if (hardwareTarget.virtual_width == target->virtual_width
&& hardwareTarget.virtual_height == target->virtual_height) {
// We are setting the native video mode, nothing special to do
hardwareTarget = *target;
} else
needsScaling = true;
} else {
// We need to enable the panel fitter
TRACE("%s: hardware mode will actually be %dx%d\n", __func__,
hardwareTarget.virtual_width, hardwareTarget.virtual_height);
TRACE("%s: hardware mode will actually be %dx%d (%s)\n", __func__,
hardwareTarget.virtual_width, hardwareTarget.virtual_height,
needsScaling ? "scaled" : "unscaled");
hardwareTarget.space = target->space;
// FIXME we should also get the refresh frequency from the target
// mode, and then "sanitize" the resulting mode we made up.
needsScaling = true;
}
} else {
#endif
{
// We don't have EDID data, try to set the requested mode directly
// We don't have VBT data, try to set the requested mode directly
hardwareTarget = *target;
}
pll_divisors divisors;
if (needsScaling)
compute_pll_divisors(&hardwareTarget, &divisors, true);
else
compute_pll_divisors(target, &divisors, true);
compute_pll_divisors(&hardwareTarget, &divisors, true);
uint32 lvds = read32(_PortRegister())
| LVDS_PORT_EN | LVDS_A0A2_CLKA_POWER_UP;
@ -583,103 +577,50 @@ LVDSPort::SetDisplayMode(display_mode* target, uint32 colorMode)
// Program general pipe config
fPipe->Configure(target);
// Program pipe PLL's (pixel_clock is *always* the hardware pixel clock)
// Program pipe PLL's (using the hardware mode timings, since that's what
// the PLL is used for)
fPipe->ConfigureClocks(divisors, hardwareTarget.timing.pixel_clock,
extraPLLFlags);
// Disable panel fitting, but enable 8 to 6-bit dithering
write32(INTEL_PANEL_FIT_CONTROL, 0x4);
// TODO: do not do this if the connected panel is 24-bit
// (I don't know how to detect that)
if (gInfo->shared_info->device_type.Generation() != 4) {
// G45: no need to power the panel off
// Power on Panel
write32(panelControl,
read32(panelControl) | PANEL_CONTROL_POWER_TARGET_ON);
read32(panelControl);
// Power on Panel
write32(panelControl, read32(panelControl) | PANEL_CONTROL_POWER_TARGET_ON);
read32(panelControl);
if (!wait_for_set(panelStatus, PANEL_STATUS_POWER_ON, 1000))
ERROR("%s: %s didn't power on within 1000ms!\n", __func__, PortName());
if (!wait_for_set(panelStatus, PANEL_STATUS_POWER_ON, 1000)) {
ERROR("%s: %s didn't power on within 1000ms!\n", __func__,
PortName());
}
}
// Program target display mode
fPipe->ConfigureTimings(target);
fPipe->ConfigureTimings(target, false);
#if 0
// update timing parameters
if (needsScaling) {
// TODO: Alternatively, it should be possible to use the panel
// fitter and scale the picture.
// TODO: Perform some sanity check, for example if the target is
// wider than the hardware mode we end up with negative borders and
// broken timings
uint32 borderWidth = hardwareTarget.timing.h_display
- target->timing.h_display;
uint32 syncWidth = hardwareTarget.timing.h_sync_end
- hardwareTarget.timing.h_sync_start;
uint32 syncCenter = target->timing.h_display
+ (hardwareTarget.timing.h_total
- target->timing.h_display) / 2;
write32(INTEL_DISPLAY_B_HTOTAL,
((uint32)(hardwareTarget.timing.h_total - 1) << 16)
| ((uint32)target->timing.h_display - 1));
write32(INTEL_DISPLAY_B_HBLANK,
((uint32)(hardwareTarget.timing.h_total - borderWidth / 2 - 1)
<< 16)
| ((uint32)target->timing.h_display + borderWidth / 2 - 1));
write32(INTEL_DISPLAY_B_HSYNC,
((uint32)(syncCenter + syncWidth / 2 - 1) << 16)
| ((uint32)syncCenter - syncWidth / 2 - 1));
uint32 borderHeight = hardwareTarget.timing.v_display
- target->timing.v_display;
uint32 syncHeight = hardwareTarget.timing.v_sync_end
- hardwareTarget.timing.v_sync_start;
syncCenter = target->timing.v_display
+ (hardwareTarget.timing.v_total
- target->timing.v_display) / 2;
write32(INTEL_DISPLAY_B_VTOTAL,
((uint32)(hardwareTarget.timing.v_total - 1) << 16)
| ((uint32)target->timing.v_display - 1));
write32(INTEL_DISPLAY_B_VBLANK,
((uint32)(hardwareTarget.timing.v_total - borderHeight / 2 - 1)
<< 16)
| ((uint32)target->timing.v_display
+ borderHeight / 2 - 1));
write32(INTEL_DISPLAY_B_VSYNC,
((uint32)(syncCenter + syncHeight / 2 - 1) << 16)
| ((uint32)syncCenter - syncHeight / 2 - 1));
// This is useful for debugging: it sets the border to red, so you
// can see what is border and what is porch (black area around the
// sync)
// write32(0x61020, 0x00FF0000);
// Enable panel fitter in automatic mode. It will figure out
// the scaling ratios automatically.
uint32 panelFitterControl = read32(INTEL_PANEL_FIT_CONTROL);
panelFitterControl |= PANEL_FITTER_ENABLED;
panelFitterControl &= ~(PANEL_FITTER_SCALING_MODE_MASK
| PANEL_FITTER_PIPE_MASK);
panelFitterControl |= PANEL_FITTER_PIPE_B;
// LVDS is always on pipe B.
write32(INTEL_PANEL_FIT_CONTROL, panelFitterControl);
} else {
write32(INTEL_DISPLAY_B_HTOTAL,
((uint32)(target->timing.h_total - 1) << 16)
| ((uint32)target->timing.h_display - 1));
write32(INTEL_DISPLAY_B_HBLANK,
((uint32)(target->timing.h_total - 1) << 16)
| ((uint32)target->timing.h_display - 1));
write32(INTEL_DISPLAY_B_HSYNC,
((uint32)(target->timing.h_sync_end - 1) << 16)
| ((uint32)target->timing.h_sync_start - 1));
write32(INTEL_DISPLAY_B_VTOTAL,
((uint32)(target->timing.v_total - 1) << 16)
| ((uint32)target->timing.v_display - 1));
write32(INTEL_DISPLAY_B_VBLANK,
((uint32)(target->timing.v_total - 1) << 16)
| ((uint32)target->timing.v_display - 1));
write32(INTEL_DISPLAY_B_VSYNC, (
(uint32)(target->timing.v_sync_end - 1) << 16)
| ((uint32)target->timing.v_sync_start - 1));
if (gInfo->shared_info->device_type.Generation() == 4) {
// Bypass the panel fitter
uint32 panelFitterControl = read32(INTEL_PANEL_FIT_CONTROL);
panelFitterControl &= ~PANEL_FITTER_ENABLED;
write32(INTEL_PANEL_FIT_CONTROL, panelFitterControl);
} else {
// Disable panel fitting, but enable 8 to 6-bit dithering
write32(INTEL_PANEL_FIT_CONTROL, 0x4);
// TODO: do not do this if the connected panel is 24-bit
// (I don't know how to detect that)
}
}
#endif
// Set fCurrentMode to our set display mode
memcpy(&fCurrentMode, target, sizeof(display_mode));