In one of my previous
post, I blindly rely on the higher level code to turn the display on and off.
Recently, I'm interested to look deeper into the i915 driver to understand how some of the mode setting code works, as explained in the good
Intel® 965 Express Chipset Family and Intel® G35 Express Chipset Graphics Controller PRM Programmer’s Reference Manual (PRM) Volume 3: Display Registers
in www.intellinuxgraphics.org.
I start my journey with linux/drivers/gpu/drm/i915/intel_display.c.
First of all, I see:
linux/drivers/gpu/drm/i915/intel_display.c:
static const struct drm_crtc_helper_funcs intel_helper_funcs = {
.dpms = intel_crtc_dpms,
.mode_fixup = intel_crtc_mode_fixup,
.mode_set = intel_crtc_mode_set,
.mode_set_base = intel_pipe_set_base,
.prepare = intel_crtc_prepare,
.commit = intel_crtc_commit,
.load_lut = intel_crtc_load_lut,
};
At a first glance, intel_crtc_mode_set() looks like a promising place to look for mode setting code. However, since it is register as a hook of drm_crtc_helper_funcs, I presume some high level code must be calling it. To understand more about the calling sequence, I decided to look for the caller first.
linux/drivers/gpu/drm/drm_crtc_helper.c:
bool drm_crtc_helper_set_mode(...)
{
...
crtc_funcs->prepare(crtc);
/* Set up the DPLL and any encoders state that needs to adjust or depend
* on the DPLL.
*/
ret = !crtc_funcs->mode_set(crtc, mode, adjusted_mode, x, y, old_fb);
...
}
So, it is apparent that the caller will perform some preparation with intel_crtc_prepare() before calling intel_crtc_mode_set().
Some of the mode switching sequence is actually documented in the PRM's section 2.2.2 Mode Switch Programming Sequences.
When we look at intel_crtc_prepare(), looks like it is using some DPMS related code to turn of the display before setting the mode.
linux/drivers/gpu/drm/i915/intel_display.c:
static void intel_crtc_prepare (struct drm_crtc *crtc)
{
struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
crtc_funcs->dpms(crtc, DRM_MODE_DPMS_OFF);
}
But crtc_funcs->dpms() is actually intel_crtc_dpms().
So,
linux/drivers/gpu/drm/i915/intel_display.c:
static void intel_crtc_dpms(struct drm_crtc *crtc, int mode)
{
...
struct drm_i915_private *dev_priv = dev->dev_private;
...
dev_priv->display.dpms(crtc, mode);
...
}
where...
linux/drivers/gpu/drm/i915/i915_drv.h:
typedef struct drm_i915_private {
...
/* Display functions */
struct drm_i915_display_funcs display;
...
} drm_i915_private_t;
Note that this hook is initialized in:
linux/drivers/gpu/drm/i915/intel_display.c:
static void intel_init_display(struct drm_device *dev)
{
...
/* We always want a DPMS function */
if (IS_IRONLAKE(dev))
dev_priv->display.dpms = ironlake_crtc_dpms;
else
dev_priv->display.dpms = i9xx_crtc_dpms;
...
}
So, in other words, intel_crtc_dpms() indirectly calls either ironlake_crtc_dpms() or i9xx_crtc_dpms(), to disable the display pipeline, depending on the GMCH type. For this discussion, lets take i9xx_crtc_dpms() as example.
linux/drivers/gpu/drm/i915/intel_display.c:
static void i9xx_crtc_dpms(struct drm_crtc *crtc, int mode)
{
...
int dpll_reg = (pipe == 0) ? DPLL_A : DPLL_B;
int dspcntr_reg = (plane == 0) ? DSPACNTR : DSPBCNTR;
int dspbase_reg = (plane == 0) ? DSPAADDR : DSPBADDR;
int pipeconf_reg = (pipe == 0) ? PIPEACONF : PIPEBCONF;
...
case DRM_MODE_DPMS_OFF:
...
/* Disable display plane */
temp = I915_READ(dspcntr_reg);
if ((temp & DISPLAY_PLANE_ENABLE) != 0) {
I915_WRITE(dspcntr_reg, temp & ~DISPLAY_PLANE_ENABLE);
/* Flush the plane changes */
I915_WRITE(dspbase_reg, I915_READ(dspbase_reg));
I915_READ(dspbase_reg);
}
if (!IS_I9XX(dev)) {
/* Wait for vblank for the disable to take effect */
intel_wait_for_vblank(dev);
}
/* Next, disable display pipes */
temp = I915_READ(pipeconf_reg);
if ((temp & PIPEACONF_ENABLE) != 0) {
I915_WRITE(pipeconf_reg, temp & ~PIPEACONF_ENABLE);
I915_READ(pipeconf_reg);
}
/* Wait for vblank for the disable to take effect. */
intel_wait_for_vblank(dev);
temp = I915_READ(dpll_reg);
if ((temp & DPLL_VCO_ENABLE) != 0) {
I915_WRITE(dpll_reg, temp & ~DPLL_VCO_ENABLE);
I915_READ(dpll_reg);
}
/* Wait for the clocks to turn off. */
udelay(150);
}
The mode switching disabling sequence is explained in the PRM as follow:
2.2.2 Mode Switch Programming Sequences
...
• Planes must be disabled before pipe is disabled or pipe timings changed.
...
Table 2-1. Mode Switch Sequences
...
Disable sequence
...
Disable ports
Disable planes (VGA or hires)
Disable pipe
Disable VGA display in 0x71400 bit 31
(Disable VGA display done after disable pipe to allow pipe to turn off when no vblank is available in native VGA mode)
If Gen4 { Wait for pipe off status }
(Wait ensures planes and pipe have completely turned off prior to disabling panelfitter then
DPLL)
Disable panelfitter
Disable DPLL
...
After the disabling sequence comes the enabling sequence, where drm_crtc_helper_set_mode() will indirectly call intel_crtc_mode_set() via the crtc_funcs hooks.
Firstly, the reference clock frequency is determined.
linux/drivers/gpu/drm/i915/intel_display.c:
static int intel_crtc_mode_set(...)
{
...
} else if (IS_I9XX(dev)) {
refclk = 96000;
...
}
The information about reference clock can be seen in the PRM:
2.5 Display Clock Control Registers (06000h–06FFFh)
Reference Frequency:
96MHz for SDVO CRT, HDMI,
96MHz or 100MHz for LVDS.
After performing some DPLL magic (magic because my shallow knowledge doesn't fully comprehend the PRM yet :-), intel_crtc_set_mode() proceed to set the new "mode":
linux/drivers/gpu/drm/i915/intel_display.c:
static int intel_crtc_mode_set(...)
{
...
/* setup pipeconf */
pipeconf = I915_READ(pipeconf_reg);
/* Set up the display plane register */
dspcntr = DISPPLANE_GAMMA_ENABLE;
...
dspcntr |= DISPLAY_PLANE_ENABLE;
pipeconf |= PIPEACONF_ENABLE;
dpll |= DPLL_VCO_ENABLE;
...
I915_WRITE(htot_reg, (adjusted_mode->crtc_hdisplay - 1) |
((adjusted_mode->crtc_htotal - 1) 16));
I915_WRITE(hblank_reg, (adjusted_mode->crtc_hblank_start - 1) |
((adjusted_mode->crtc_hblank_end - 1) 16));
I915_WRITE(hsync_reg, (adjusted_mode->crtc_hsync_start - 1) |
((adjusted_mode->crtc_hsync_end - 1) 16));
I915_WRITE(vtot_reg, (adjusted_mode->crtc_vdisplay - 1) |
((adjusted_mode->crtc_vtotal - 1) 16));
I915_WRITE(vblank_reg, (adjusted_mode->crtc_vblank_start - 1) |
((adjusted_mode->crtc_vblank_end - 1) 16));
I915_WRITE(vsync_reg, (adjusted_mode->crtc_vsync_start - 1) |
((adjusted_mode->crtc_vsync_end - 1) 16));
...
I915_WRITE(pipesrc_reg, ((mode->hdisplay - 1) 16) | (mode->vdisplay - 1));
...
I915_WRITE(pipeconf_reg, pipeconf);
I915_READ(pipeconf_reg);
intel_wait_for_vblank(dev);
...
I915_WRITE(dspcntr_reg, dspcntr);
...
}
Much of the information about mode adjustment code can be found in the PRM section 2.7 Display Pipeline / Port Registers (60000h–6FFFFh), 2.10.1.3 PIPEACONF—Pipe A Configuration Register and also Enable sequence in 2.2.2 Mode Switch Programming Sequences.
I guess that is the overall flow of what happen during a KMS mode setting like when we use xrandr to select another resolution.
Having said that, this is just the beginning of my investigation and nothing here is authoritative. If any of you understand better, please do enlighten me. Any advise is welcome.