forked from Minki/linux
40478455fe
In commit
commit 6375b768a9
Author: Ville Syrjälä <ville.syrjala@linux.intel.com>
Date: Mon Mar 3 11:33:36 2014 +0200
drm/i915: Reject >165MHz modes w/ DVI monitors
the driver started to filter out display modes which exceed the
single-link DVI 165Mz dotclock limits when the monitor doesn't report
itself as being HDMI compliant. The intent was to filter out all
EDID derived modes that require dual-link DVI to operate since we
don't support dual-link.
However the patch went a bit too far and also causes the driver to reject
such modes even when specified by the user. Normally we don't check the
sink limitations when setting a mode from the user. This allows the user
to specify any mode whether the sink reports to support it or not. This
can be useful since often the sinks support more modes than they report
in the EDID.
So relax the checks a bit, and apply the single-link DVI dotclock limit
only when filtering the mode list, and ignore the limit when setting
a user specified mode.
Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=72961
Tested-by: Nicholas Vinson <nvinson@comcast.net>
Cc: stable@vger.kernel.org [3.14]
Reviewed-by: Daniel Vetter <daniel@ffwll.ch>
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Jani Nikula <jani.nikula@intel.com>
1362 lines
39 KiB
C
1362 lines
39 KiB
C
/*
|
|
* Copyright 2006 Dave Airlie <airlied@linux.ie>
|
|
* Copyright © 2006-2009 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>
|
|
* Jesse Barnes <jesse.barnes@intel.com>
|
|
*/
|
|
|
|
#include <linux/i2c.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/hdmi.h>
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_crtc.h>
|
|
#include <drm/drm_edid.h>
|
|
#include "intel_drv.h"
|
|
#include <drm/i915_drm.h>
|
|
#include "i915_drv.h"
|
|
|
|
static struct drm_device *intel_hdmi_to_dev(struct intel_hdmi *intel_hdmi)
|
|
{
|
|
return hdmi_to_dig_port(intel_hdmi)->base.base.dev;
|
|
}
|
|
|
|
static void
|
|
assert_hdmi_port_disabled(struct intel_hdmi *intel_hdmi)
|
|
{
|
|
struct drm_device *dev = intel_hdmi_to_dev(intel_hdmi);
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
uint32_t enabled_bits;
|
|
|
|
enabled_bits = HAS_DDI(dev) ? DDI_BUF_CTL_ENABLE : SDVO_ENABLE;
|
|
|
|
WARN(I915_READ(intel_hdmi->hdmi_reg) & enabled_bits,
|
|
"HDMI port enabled, expecting disabled\n");
|
|
}
|
|
|
|
struct intel_hdmi *enc_to_intel_hdmi(struct drm_encoder *encoder)
|
|
{
|
|
struct intel_digital_port *intel_dig_port =
|
|
container_of(encoder, struct intel_digital_port, base.base);
|
|
return &intel_dig_port->hdmi;
|
|
}
|
|
|
|
static struct intel_hdmi *intel_attached_hdmi(struct drm_connector *connector)
|
|
{
|
|
return enc_to_intel_hdmi(&intel_attached_encoder(connector)->base);
|
|
}
|
|
|
|
static u32 g4x_infoframe_index(enum hdmi_infoframe_type type)
|
|
{
|
|
switch (type) {
|
|
case HDMI_INFOFRAME_TYPE_AVI:
|
|
return VIDEO_DIP_SELECT_AVI;
|
|
case HDMI_INFOFRAME_TYPE_SPD:
|
|
return VIDEO_DIP_SELECT_SPD;
|
|
case HDMI_INFOFRAME_TYPE_VENDOR:
|
|
return VIDEO_DIP_SELECT_VENDOR;
|
|
default:
|
|
DRM_DEBUG_DRIVER("unknown info frame type %d\n", type);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static u32 g4x_infoframe_enable(enum hdmi_infoframe_type type)
|
|
{
|
|
switch (type) {
|
|
case HDMI_INFOFRAME_TYPE_AVI:
|
|
return VIDEO_DIP_ENABLE_AVI;
|
|
case HDMI_INFOFRAME_TYPE_SPD:
|
|
return VIDEO_DIP_ENABLE_SPD;
|
|
case HDMI_INFOFRAME_TYPE_VENDOR:
|
|
return VIDEO_DIP_ENABLE_VENDOR;
|
|
default:
|
|
DRM_DEBUG_DRIVER("unknown info frame type %d\n", type);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static u32 hsw_infoframe_enable(enum hdmi_infoframe_type type)
|
|
{
|
|
switch (type) {
|
|
case HDMI_INFOFRAME_TYPE_AVI:
|
|
return VIDEO_DIP_ENABLE_AVI_HSW;
|
|
case HDMI_INFOFRAME_TYPE_SPD:
|
|
return VIDEO_DIP_ENABLE_SPD_HSW;
|
|
case HDMI_INFOFRAME_TYPE_VENDOR:
|
|
return VIDEO_DIP_ENABLE_VS_HSW;
|
|
default:
|
|
DRM_DEBUG_DRIVER("unknown info frame type %d\n", type);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static u32 hsw_infoframe_data_reg(enum hdmi_infoframe_type type,
|
|
enum transcoder cpu_transcoder,
|
|
struct drm_i915_private *dev_priv)
|
|
{
|
|
switch (type) {
|
|
case HDMI_INFOFRAME_TYPE_AVI:
|
|
return HSW_TVIDEO_DIP_AVI_DATA(cpu_transcoder);
|
|
case HDMI_INFOFRAME_TYPE_SPD:
|
|
return HSW_TVIDEO_DIP_SPD_DATA(cpu_transcoder);
|
|
case HDMI_INFOFRAME_TYPE_VENDOR:
|
|
return HSW_TVIDEO_DIP_VS_DATA(cpu_transcoder);
|
|
default:
|
|
DRM_DEBUG_DRIVER("unknown info frame type %d\n", type);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void g4x_write_infoframe(struct drm_encoder *encoder,
|
|
enum hdmi_infoframe_type type,
|
|
const void *frame, ssize_t len)
|
|
{
|
|
const uint32_t *data = frame;
|
|
struct drm_device *dev = encoder->dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
u32 val = I915_READ(VIDEO_DIP_CTL);
|
|
int i;
|
|
|
|
WARN(!(val & VIDEO_DIP_ENABLE), "Writing DIP with CTL reg disabled\n");
|
|
|
|
val &= ~(VIDEO_DIP_SELECT_MASK | 0xf); /* clear DIP data offset */
|
|
val |= g4x_infoframe_index(type);
|
|
|
|
val &= ~g4x_infoframe_enable(type);
|
|
|
|
I915_WRITE(VIDEO_DIP_CTL, val);
|
|
|
|
mmiowb();
|
|
for (i = 0; i < len; i += 4) {
|
|
I915_WRITE(VIDEO_DIP_DATA, *data);
|
|
data++;
|
|
}
|
|
/* Write every possible data byte to force correct ECC calculation. */
|
|
for (; i < VIDEO_DIP_DATA_SIZE; i += 4)
|
|
I915_WRITE(VIDEO_DIP_DATA, 0);
|
|
mmiowb();
|
|
|
|
val |= g4x_infoframe_enable(type);
|
|
val &= ~VIDEO_DIP_FREQ_MASK;
|
|
val |= VIDEO_DIP_FREQ_VSYNC;
|
|
|
|
I915_WRITE(VIDEO_DIP_CTL, val);
|
|
POSTING_READ(VIDEO_DIP_CTL);
|
|
}
|
|
|
|
static void ibx_write_infoframe(struct drm_encoder *encoder,
|
|
enum hdmi_infoframe_type type,
|
|
const void *frame, ssize_t len)
|
|
{
|
|
const uint32_t *data = frame;
|
|
struct drm_device *dev = encoder->dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
struct intel_crtc *intel_crtc = to_intel_crtc(encoder->crtc);
|
|
int i, reg = TVIDEO_DIP_CTL(intel_crtc->pipe);
|
|
u32 val = I915_READ(reg);
|
|
|
|
WARN(!(val & VIDEO_DIP_ENABLE), "Writing DIP with CTL reg disabled\n");
|
|
|
|
val &= ~(VIDEO_DIP_SELECT_MASK | 0xf); /* clear DIP data offset */
|
|
val |= g4x_infoframe_index(type);
|
|
|
|
val &= ~g4x_infoframe_enable(type);
|
|
|
|
I915_WRITE(reg, val);
|
|
|
|
mmiowb();
|
|
for (i = 0; i < len; i += 4) {
|
|
I915_WRITE(TVIDEO_DIP_DATA(intel_crtc->pipe), *data);
|
|
data++;
|
|
}
|
|
/* Write every possible data byte to force correct ECC calculation. */
|
|
for (; i < VIDEO_DIP_DATA_SIZE; i += 4)
|
|
I915_WRITE(TVIDEO_DIP_DATA(intel_crtc->pipe), 0);
|
|
mmiowb();
|
|
|
|
val |= g4x_infoframe_enable(type);
|
|
val &= ~VIDEO_DIP_FREQ_MASK;
|
|
val |= VIDEO_DIP_FREQ_VSYNC;
|
|
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
}
|
|
|
|
static void cpt_write_infoframe(struct drm_encoder *encoder,
|
|
enum hdmi_infoframe_type type,
|
|
const void *frame, ssize_t len)
|
|
{
|
|
const uint32_t *data = frame;
|
|
struct drm_device *dev = encoder->dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
struct intel_crtc *intel_crtc = to_intel_crtc(encoder->crtc);
|
|
int i, reg = TVIDEO_DIP_CTL(intel_crtc->pipe);
|
|
u32 val = I915_READ(reg);
|
|
|
|
WARN(!(val & VIDEO_DIP_ENABLE), "Writing DIP with CTL reg disabled\n");
|
|
|
|
val &= ~(VIDEO_DIP_SELECT_MASK | 0xf); /* clear DIP data offset */
|
|
val |= g4x_infoframe_index(type);
|
|
|
|
/* The DIP control register spec says that we need to update the AVI
|
|
* infoframe without clearing its enable bit */
|
|
if (type != HDMI_INFOFRAME_TYPE_AVI)
|
|
val &= ~g4x_infoframe_enable(type);
|
|
|
|
I915_WRITE(reg, val);
|
|
|
|
mmiowb();
|
|
for (i = 0; i < len; i += 4) {
|
|
I915_WRITE(TVIDEO_DIP_DATA(intel_crtc->pipe), *data);
|
|
data++;
|
|
}
|
|
/* Write every possible data byte to force correct ECC calculation. */
|
|
for (; i < VIDEO_DIP_DATA_SIZE; i += 4)
|
|
I915_WRITE(TVIDEO_DIP_DATA(intel_crtc->pipe), 0);
|
|
mmiowb();
|
|
|
|
val |= g4x_infoframe_enable(type);
|
|
val &= ~VIDEO_DIP_FREQ_MASK;
|
|
val |= VIDEO_DIP_FREQ_VSYNC;
|
|
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
}
|
|
|
|
static void vlv_write_infoframe(struct drm_encoder *encoder,
|
|
enum hdmi_infoframe_type type,
|
|
const void *frame, ssize_t len)
|
|
{
|
|
const uint32_t *data = frame;
|
|
struct drm_device *dev = encoder->dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
struct intel_crtc *intel_crtc = to_intel_crtc(encoder->crtc);
|
|
int i, reg = VLV_TVIDEO_DIP_CTL(intel_crtc->pipe);
|
|
u32 val = I915_READ(reg);
|
|
|
|
WARN(!(val & VIDEO_DIP_ENABLE), "Writing DIP with CTL reg disabled\n");
|
|
|
|
val &= ~(VIDEO_DIP_SELECT_MASK | 0xf); /* clear DIP data offset */
|
|
val |= g4x_infoframe_index(type);
|
|
|
|
val &= ~g4x_infoframe_enable(type);
|
|
|
|
I915_WRITE(reg, val);
|
|
|
|
mmiowb();
|
|
for (i = 0; i < len; i += 4) {
|
|
I915_WRITE(VLV_TVIDEO_DIP_DATA(intel_crtc->pipe), *data);
|
|
data++;
|
|
}
|
|
/* Write every possible data byte to force correct ECC calculation. */
|
|
for (; i < VIDEO_DIP_DATA_SIZE; i += 4)
|
|
I915_WRITE(VLV_TVIDEO_DIP_DATA(intel_crtc->pipe), 0);
|
|
mmiowb();
|
|
|
|
val |= g4x_infoframe_enable(type);
|
|
val &= ~VIDEO_DIP_FREQ_MASK;
|
|
val |= VIDEO_DIP_FREQ_VSYNC;
|
|
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
}
|
|
|
|
static void hsw_write_infoframe(struct drm_encoder *encoder,
|
|
enum hdmi_infoframe_type type,
|
|
const void *frame, ssize_t len)
|
|
{
|
|
const uint32_t *data = frame;
|
|
struct drm_device *dev = encoder->dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
struct intel_crtc *intel_crtc = to_intel_crtc(encoder->crtc);
|
|
u32 ctl_reg = HSW_TVIDEO_DIP_CTL(intel_crtc->config.cpu_transcoder);
|
|
u32 data_reg;
|
|
int i;
|
|
u32 val = I915_READ(ctl_reg);
|
|
|
|
data_reg = hsw_infoframe_data_reg(type,
|
|
intel_crtc->config.cpu_transcoder,
|
|
dev_priv);
|
|
if (data_reg == 0)
|
|
return;
|
|
|
|
val &= ~hsw_infoframe_enable(type);
|
|
I915_WRITE(ctl_reg, val);
|
|
|
|
mmiowb();
|
|
for (i = 0; i < len; i += 4) {
|
|
I915_WRITE(data_reg + i, *data);
|
|
data++;
|
|
}
|
|
/* Write every possible data byte to force correct ECC calculation. */
|
|
for (; i < VIDEO_DIP_DATA_SIZE; i += 4)
|
|
I915_WRITE(data_reg + i, 0);
|
|
mmiowb();
|
|
|
|
val |= hsw_infoframe_enable(type);
|
|
I915_WRITE(ctl_reg, val);
|
|
POSTING_READ(ctl_reg);
|
|
}
|
|
|
|
/*
|
|
* The data we write to the DIP data buffer registers is 1 byte bigger than the
|
|
* HDMI infoframe size because of an ECC/reserved byte at position 3 (starting
|
|
* at 0). It's also a byte used by DisplayPort so the same DIP registers can be
|
|
* used for both technologies.
|
|
*
|
|
* DW0: Reserved/ECC/DP | HB2 | HB1 | HB0
|
|
* DW1: DB3 | DB2 | DB1 | DB0
|
|
* DW2: DB7 | DB6 | DB5 | DB4
|
|
* DW3: ...
|
|
*
|
|
* (HB is Header Byte, DB is Data Byte)
|
|
*
|
|
* The hdmi pack() functions don't know about that hardware specific hole so we
|
|
* trick them by giving an offset into the buffer and moving back the header
|
|
* bytes by one.
|
|
*/
|
|
static void intel_write_infoframe(struct drm_encoder *encoder,
|
|
union hdmi_infoframe *frame)
|
|
{
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
|
|
uint8_t buffer[VIDEO_DIP_DATA_SIZE];
|
|
ssize_t len;
|
|
|
|
/* see comment above for the reason for this offset */
|
|
len = hdmi_infoframe_pack(frame, buffer + 1, sizeof(buffer) - 1);
|
|
if (len < 0)
|
|
return;
|
|
|
|
/* Insert the 'hole' (see big comment above) at position 3 */
|
|
buffer[0] = buffer[1];
|
|
buffer[1] = buffer[2];
|
|
buffer[2] = buffer[3];
|
|
buffer[3] = 0;
|
|
len++;
|
|
|
|
intel_hdmi->write_infoframe(encoder, frame->any.type, buffer, len);
|
|
}
|
|
|
|
static void intel_hdmi_set_avi_infoframe(struct drm_encoder *encoder,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
|
|
struct intel_crtc *intel_crtc = to_intel_crtc(encoder->crtc);
|
|
union hdmi_infoframe frame;
|
|
int ret;
|
|
|
|
ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi,
|
|
adjusted_mode);
|
|
if (ret < 0) {
|
|
DRM_ERROR("couldn't fill AVI infoframe\n");
|
|
return;
|
|
}
|
|
|
|
if (intel_hdmi->rgb_quant_range_selectable) {
|
|
if (intel_crtc->config.limited_color_range)
|
|
frame.avi.quantization_range =
|
|
HDMI_QUANTIZATION_RANGE_LIMITED;
|
|
else
|
|
frame.avi.quantization_range =
|
|
HDMI_QUANTIZATION_RANGE_FULL;
|
|
}
|
|
|
|
intel_write_infoframe(encoder, &frame);
|
|
}
|
|
|
|
static void intel_hdmi_set_spd_infoframe(struct drm_encoder *encoder)
|
|
{
|
|
union hdmi_infoframe frame;
|
|
int ret;
|
|
|
|
ret = hdmi_spd_infoframe_init(&frame.spd, "Intel", "Integrated gfx");
|
|
if (ret < 0) {
|
|
DRM_ERROR("couldn't fill SPD infoframe\n");
|
|
return;
|
|
}
|
|
|
|
frame.spd.sdi = HDMI_SPD_SDI_PC;
|
|
|
|
intel_write_infoframe(encoder, &frame);
|
|
}
|
|
|
|
static void
|
|
intel_hdmi_set_hdmi_infoframe(struct drm_encoder *encoder,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
union hdmi_infoframe frame;
|
|
int ret;
|
|
|
|
ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi,
|
|
adjusted_mode);
|
|
if (ret < 0)
|
|
return;
|
|
|
|
intel_write_infoframe(encoder, &frame);
|
|
}
|
|
|
|
static void g4x_set_infoframes(struct drm_encoder *encoder,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct drm_i915_private *dev_priv = encoder->dev->dev_private;
|
|
struct intel_digital_port *intel_dig_port = enc_to_dig_port(encoder);
|
|
struct intel_hdmi *intel_hdmi = &intel_dig_port->hdmi;
|
|
u32 reg = VIDEO_DIP_CTL;
|
|
u32 val = I915_READ(reg);
|
|
u32 port = VIDEO_DIP_PORT(intel_dig_port->port);
|
|
|
|
assert_hdmi_port_disabled(intel_hdmi);
|
|
|
|
/* If the registers were not initialized yet, they might be zeroes,
|
|
* which means we're selecting the AVI DIP and we're setting its
|
|
* frequency to once. This seems to really confuse the HW and make
|
|
* things stop working (the register spec says the AVI always needs to
|
|
* be sent every VSync). So here we avoid writing to the register more
|
|
* than we need and also explicitly select the AVI DIP and explicitly
|
|
* set its frequency to every VSync. Avoiding to write it twice seems to
|
|
* be enough to solve the problem, but being defensive shouldn't hurt us
|
|
* either. */
|
|
val |= VIDEO_DIP_SELECT_AVI | VIDEO_DIP_FREQ_VSYNC;
|
|
|
|
if (!intel_hdmi->has_hdmi_sink) {
|
|
if (!(val & VIDEO_DIP_ENABLE))
|
|
return;
|
|
val &= ~VIDEO_DIP_ENABLE;
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
return;
|
|
}
|
|
|
|
if (port != (val & VIDEO_DIP_PORT_MASK)) {
|
|
if (val & VIDEO_DIP_ENABLE) {
|
|
val &= ~VIDEO_DIP_ENABLE;
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
}
|
|
val &= ~VIDEO_DIP_PORT_MASK;
|
|
val |= port;
|
|
}
|
|
|
|
val |= VIDEO_DIP_ENABLE;
|
|
val &= ~VIDEO_DIP_ENABLE_VENDOR;
|
|
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
|
|
intel_hdmi_set_avi_infoframe(encoder, adjusted_mode);
|
|
intel_hdmi_set_spd_infoframe(encoder);
|
|
intel_hdmi_set_hdmi_infoframe(encoder, adjusted_mode);
|
|
}
|
|
|
|
static void ibx_set_infoframes(struct drm_encoder *encoder,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct drm_i915_private *dev_priv = encoder->dev->dev_private;
|
|
struct intel_crtc *intel_crtc = to_intel_crtc(encoder->crtc);
|
|
struct intel_digital_port *intel_dig_port = enc_to_dig_port(encoder);
|
|
struct intel_hdmi *intel_hdmi = &intel_dig_port->hdmi;
|
|
u32 reg = TVIDEO_DIP_CTL(intel_crtc->pipe);
|
|
u32 val = I915_READ(reg);
|
|
u32 port = VIDEO_DIP_PORT(intel_dig_port->port);
|
|
|
|
assert_hdmi_port_disabled(intel_hdmi);
|
|
|
|
/* See the big comment in g4x_set_infoframes() */
|
|
val |= VIDEO_DIP_SELECT_AVI | VIDEO_DIP_FREQ_VSYNC;
|
|
|
|
if (!intel_hdmi->has_hdmi_sink) {
|
|
if (!(val & VIDEO_DIP_ENABLE))
|
|
return;
|
|
val &= ~VIDEO_DIP_ENABLE;
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
return;
|
|
}
|
|
|
|
if (port != (val & VIDEO_DIP_PORT_MASK)) {
|
|
if (val & VIDEO_DIP_ENABLE) {
|
|
val &= ~VIDEO_DIP_ENABLE;
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
}
|
|
val &= ~VIDEO_DIP_PORT_MASK;
|
|
val |= port;
|
|
}
|
|
|
|
val |= VIDEO_DIP_ENABLE;
|
|
val &= ~(VIDEO_DIP_ENABLE_VENDOR | VIDEO_DIP_ENABLE_GAMUT |
|
|
VIDEO_DIP_ENABLE_GCP);
|
|
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
|
|
intel_hdmi_set_avi_infoframe(encoder, adjusted_mode);
|
|
intel_hdmi_set_spd_infoframe(encoder);
|
|
intel_hdmi_set_hdmi_infoframe(encoder, adjusted_mode);
|
|
}
|
|
|
|
static void cpt_set_infoframes(struct drm_encoder *encoder,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct drm_i915_private *dev_priv = encoder->dev->dev_private;
|
|
struct intel_crtc *intel_crtc = to_intel_crtc(encoder->crtc);
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
|
|
u32 reg = TVIDEO_DIP_CTL(intel_crtc->pipe);
|
|
u32 val = I915_READ(reg);
|
|
|
|
assert_hdmi_port_disabled(intel_hdmi);
|
|
|
|
/* See the big comment in g4x_set_infoframes() */
|
|
val |= VIDEO_DIP_SELECT_AVI | VIDEO_DIP_FREQ_VSYNC;
|
|
|
|
if (!intel_hdmi->has_hdmi_sink) {
|
|
if (!(val & VIDEO_DIP_ENABLE))
|
|
return;
|
|
val &= ~(VIDEO_DIP_ENABLE | VIDEO_DIP_ENABLE_AVI);
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
return;
|
|
}
|
|
|
|
/* Set both together, unset both together: see the spec. */
|
|
val |= VIDEO_DIP_ENABLE | VIDEO_DIP_ENABLE_AVI;
|
|
val &= ~(VIDEO_DIP_ENABLE_VENDOR | VIDEO_DIP_ENABLE_GAMUT |
|
|
VIDEO_DIP_ENABLE_GCP);
|
|
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
|
|
intel_hdmi_set_avi_infoframe(encoder, adjusted_mode);
|
|
intel_hdmi_set_spd_infoframe(encoder);
|
|
intel_hdmi_set_hdmi_infoframe(encoder, adjusted_mode);
|
|
}
|
|
|
|
static void vlv_set_infoframes(struct drm_encoder *encoder,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct drm_i915_private *dev_priv = encoder->dev->dev_private;
|
|
struct intel_crtc *intel_crtc = to_intel_crtc(encoder->crtc);
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
|
|
u32 reg = VLV_TVIDEO_DIP_CTL(intel_crtc->pipe);
|
|
u32 val = I915_READ(reg);
|
|
|
|
assert_hdmi_port_disabled(intel_hdmi);
|
|
|
|
/* See the big comment in g4x_set_infoframes() */
|
|
val |= VIDEO_DIP_SELECT_AVI | VIDEO_DIP_FREQ_VSYNC;
|
|
|
|
if (!intel_hdmi->has_hdmi_sink) {
|
|
if (!(val & VIDEO_DIP_ENABLE))
|
|
return;
|
|
val &= ~VIDEO_DIP_ENABLE;
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
return;
|
|
}
|
|
|
|
val |= VIDEO_DIP_ENABLE;
|
|
val &= ~(VIDEO_DIP_ENABLE_VENDOR | VIDEO_DIP_ENABLE_GAMUT |
|
|
VIDEO_DIP_ENABLE_GCP);
|
|
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
|
|
intel_hdmi_set_avi_infoframe(encoder, adjusted_mode);
|
|
intel_hdmi_set_spd_infoframe(encoder);
|
|
intel_hdmi_set_hdmi_infoframe(encoder, adjusted_mode);
|
|
}
|
|
|
|
static void hsw_set_infoframes(struct drm_encoder *encoder,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct drm_i915_private *dev_priv = encoder->dev->dev_private;
|
|
struct intel_crtc *intel_crtc = to_intel_crtc(encoder->crtc);
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
|
|
u32 reg = HSW_TVIDEO_DIP_CTL(intel_crtc->config.cpu_transcoder);
|
|
u32 val = I915_READ(reg);
|
|
|
|
assert_hdmi_port_disabled(intel_hdmi);
|
|
|
|
if (!intel_hdmi->has_hdmi_sink) {
|
|
I915_WRITE(reg, 0);
|
|
POSTING_READ(reg);
|
|
return;
|
|
}
|
|
|
|
val &= ~(VIDEO_DIP_ENABLE_VSC_HSW | VIDEO_DIP_ENABLE_GCP_HSW |
|
|
VIDEO_DIP_ENABLE_VS_HSW | VIDEO_DIP_ENABLE_GMP_HSW);
|
|
|
|
I915_WRITE(reg, val);
|
|
POSTING_READ(reg);
|
|
|
|
intel_hdmi_set_avi_infoframe(encoder, adjusted_mode);
|
|
intel_hdmi_set_spd_infoframe(encoder);
|
|
intel_hdmi_set_hdmi_infoframe(encoder, adjusted_mode);
|
|
}
|
|
|
|
static void intel_hdmi_mode_set(struct intel_encoder *encoder)
|
|
{
|
|
struct drm_device *dev = encoder->base.dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
struct intel_crtc *crtc = to_intel_crtc(encoder->base.crtc);
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(&encoder->base);
|
|
struct drm_display_mode *adjusted_mode = &crtc->config.adjusted_mode;
|
|
u32 hdmi_val;
|
|
|
|
hdmi_val = SDVO_ENCODING_HDMI;
|
|
if (!HAS_PCH_SPLIT(dev))
|
|
hdmi_val |= intel_hdmi->color_range;
|
|
if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC)
|
|
hdmi_val |= SDVO_VSYNC_ACTIVE_HIGH;
|
|
if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC)
|
|
hdmi_val |= SDVO_HSYNC_ACTIVE_HIGH;
|
|
|
|
if (crtc->config.pipe_bpp > 24)
|
|
hdmi_val |= HDMI_COLOR_FORMAT_12bpc;
|
|
else
|
|
hdmi_val |= SDVO_COLOR_FORMAT_8bpc;
|
|
|
|
/* Required on CPT */
|
|
if (intel_hdmi->has_hdmi_sink && HAS_PCH_CPT(dev))
|
|
hdmi_val |= HDMI_MODE_SELECT_HDMI;
|
|
|
|
if (intel_hdmi->has_audio) {
|
|
DRM_DEBUG_DRIVER("Enabling HDMI audio on pipe %c\n",
|
|
pipe_name(crtc->pipe));
|
|
hdmi_val |= SDVO_AUDIO_ENABLE;
|
|
hdmi_val |= HDMI_MODE_SELECT_HDMI;
|
|
intel_write_eld(&encoder->base, adjusted_mode);
|
|
}
|
|
|
|
if (HAS_PCH_CPT(dev))
|
|
hdmi_val |= SDVO_PIPE_SEL_CPT(crtc->pipe);
|
|
else
|
|
hdmi_val |= SDVO_PIPE_SEL(crtc->pipe);
|
|
|
|
I915_WRITE(intel_hdmi->hdmi_reg, hdmi_val);
|
|
POSTING_READ(intel_hdmi->hdmi_reg);
|
|
|
|
intel_hdmi->set_infoframes(&encoder->base, adjusted_mode);
|
|
}
|
|
|
|
static bool intel_hdmi_get_hw_state(struct intel_encoder *encoder,
|
|
enum pipe *pipe)
|
|
{
|
|
struct drm_device *dev = encoder->base.dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(&encoder->base);
|
|
enum intel_display_power_domain power_domain;
|
|
u32 tmp;
|
|
|
|
power_domain = intel_display_port_power_domain(encoder);
|
|
if (!intel_display_power_enabled(dev_priv, power_domain))
|
|
return false;
|
|
|
|
tmp = I915_READ(intel_hdmi->hdmi_reg);
|
|
|
|
if (!(tmp & SDVO_ENABLE))
|
|
return false;
|
|
|
|
if (HAS_PCH_CPT(dev))
|
|
*pipe = PORT_TO_PIPE_CPT(tmp);
|
|
else
|
|
*pipe = PORT_TO_PIPE(tmp);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void intel_hdmi_get_config(struct intel_encoder *encoder,
|
|
struct intel_crtc_config *pipe_config)
|
|
{
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(&encoder->base);
|
|
struct drm_i915_private *dev_priv = encoder->base.dev->dev_private;
|
|
u32 tmp, flags = 0;
|
|
int dotclock;
|
|
|
|
tmp = I915_READ(intel_hdmi->hdmi_reg);
|
|
|
|
if (tmp & SDVO_HSYNC_ACTIVE_HIGH)
|
|
flags |= DRM_MODE_FLAG_PHSYNC;
|
|
else
|
|
flags |= DRM_MODE_FLAG_NHSYNC;
|
|
|
|
if (tmp & SDVO_VSYNC_ACTIVE_HIGH)
|
|
flags |= DRM_MODE_FLAG_PVSYNC;
|
|
else
|
|
flags |= DRM_MODE_FLAG_NVSYNC;
|
|
|
|
pipe_config->adjusted_mode.flags |= flags;
|
|
|
|
if ((tmp & SDVO_COLOR_FORMAT_MASK) == HDMI_COLOR_FORMAT_12bpc)
|
|
dotclock = pipe_config->port_clock * 2 / 3;
|
|
else
|
|
dotclock = pipe_config->port_clock;
|
|
|
|
if (HAS_PCH_SPLIT(dev_priv->dev))
|
|
ironlake_check_encoder_dotclock(pipe_config, dotclock);
|
|
|
|
pipe_config->adjusted_mode.crtc_clock = dotclock;
|
|
}
|
|
|
|
static void intel_enable_hdmi(struct intel_encoder *encoder)
|
|
{
|
|
struct drm_device *dev = encoder->base.dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
struct intel_crtc *intel_crtc = to_intel_crtc(encoder->base.crtc);
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(&encoder->base);
|
|
u32 temp;
|
|
u32 enable_bits = SDVO_ENABLE;
|
|
|
|
if (intel_hdmi->has_audio)
|
|
enable_bits |= SDVO_AUDIO_ENABLE;
|
|
|
|
temp = I915_READ(intel_hdmi->hdmi_reg);
|
|
|
|
/* HW workaround for IBX, we need to move the port to transcoder A
|
|
* before disabling it, so restore the transcoder select bit here. */
|
|
if (HAS_PCH_IBX(dev))
|
|
enable_bits |= SDVO_PIPE_SEL(intel_crtc->pipe);
|
|
|
|
/* HW workaround, need to toggle enable bit off and on for 12bpc, but
|
|
* we do this anyway which shows more stable in testing.
|
|
*/
|
|
if (HAS_PCH_SPLIT(dev)) {
|
|
I915_WRITE(intel_hdmi->hdmi_reg, temp & ~SDVO_ENABLE);
|
|
POSTING_READ(intel_hdmi->hdmi_reg);
|
|
}
|
|
|
|
temp |= enable_bits;
|
|
|
|
I915_WRITE(intel_hdmi->hdmi_reg, temp);
|
|
POSTING_READ(intel_hdmi->hdmi_reg);
|
|
|
|
/* HW workaround, need to write this twice for issue that may result
|
|
* in first write getting masked.
|
|
*/
|
|
if (HAS_PCH_SPLIT(dev)) {
|
|
I915_WRITE(intel_hdmi->hdmi_reg, temp);
|
|
POSTING_READ(intel_hdmi->hdmi_reg);
|
|
}
|
|
}
|
|
|
|
static void vlv_enable_hdmi(struct intel_encoder *encoder)
|
|
{
|
|
}
|
|
|
|
static void intel_disable_hdmi(struct intel_encoder *encoder)
|
|
{
|
|
struct drm_device *dev = encoder->base.dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(&encoder->base);
|
|
u32 temp;
|
|
u32 enable_bits = SDVO_ENABLE | SDVO_AUDIO_ENABLE;
|
|
|
|
temp = I915_READ(intel_hdmi->hdmi_reg);
|
|
|
|
/* HW workaround for IBX, we need to move the port to transcoder A
|
|
* before disabling it. */
|
|
if (HAS_PCH_IBX(dev)) {
|
|
struct drm_crtc *crtc = encoder->base.crtc;
|
|
int pipe = crtc ? to_intel_crtc(crtc)->pipe : -1;
|
|
|
|
if (temp & SDVO_PIPE_B_SELECT) {
|
|
temp &= ~SDVO_PIPE_B_SELECT;
|
|
I915_WRITE(intel_hdmi->hdmi_reg, temp);
|
|
POSTING_READ(intel_hdmi->hdmi_reg);
|
|
|
|
/* Again we need to write this twice. */
|
|
I915_WRITE(intel_hdmi->hdmi_reg, temp);
|
|
POSTING_READ(intel_hdmi->hdmi_reg);
|
|
|
|
/* Transcoder selection bits only update
|
|
* effectively on vblank. */
|
|
if (crtc)
|
|
intel_wait_for_vblank(dev, pipe);
|
|
else
|
|
msleep(50);
|
|
}
|
|
}
|
|
|
|
/* HW workaround, need to toggle enable bit off and on for 12bpc, but
|
|
* we do this anyway which shows more stable in testing.
|
|
*/
|
|
if (HAS_PCH_SPLIT(dev)) {
|
|
I915_WRITE(intel_hdmi->hdmi_reg, temp & ~SDVO_ENABLE);
|
|
POSTING_READ(intel_hdmi->hdmi_reg);
|
|
}
|
|
|
|
temp &= ~enable_bits;
|
|
|
|
I915_WRITE(intel_hdmi->hdmi_reg, temp);
|
|
POSTING_READ(intel_hdmi->hdmi_reg);
|
|
|
|
/* HW workaround, need to write this twice for issue that may result
|
|
* in first write getting masked.
|
|
*/
|
|
if (HAS_PCH_SPLIT(dev)) {
|
|
I915_WRITE(intel_hdmi->hdmi_reg, temp);
|
|
POSTING_READ(intel_hdmi->hdmi_reg);
|
|
}
|
|
}
|
|
|
|
static int hdmi_portclock_limit(struct intel_hdmi *hdmi, bool respect_dvi_limit)
|
|
{
|
|
struct drm_device *dev = intel_hdmi_to_dev(hdmi);
|
|
|
|
if ((respect_dvi_limit && !hdmi->has_hdmi_sink) || IS_G4X(dev))
|
|
return 165000;
|
|
else if (IS_HASWELL(dev) || INTEL_INFO(dev)->gen >= 8)
|
|
return 300000;
|
|
else
|
|
return 225000;
|
|
}
|
|
|
|
static enum drm_mode_status
|
|
intel_hdmi_mode_valid(struct drm_connector *connector,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
if (mode->clock > hdmi_portclock_limit(intel_attached_hdmi(connector),
|
|
true))
|
|
return MODE_CLOCK_HIGH;
|
|
if (mode->clock < 20000)
|
|
return MODE_CLOCK_LOW;
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
|
|
return MODE_NO_DBLESCAN;
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static bool hdmi_12bpc_possible(struct intel_crtc *crtc)
|
|
{
|
|
struct drm_device *dev = crtc->base.dev;
|
|
struct intel_encoder *encoder;
|
|
int count = 0, count_hdmi = 0;
|
|
|
|
if (!HAS_PCH_SPLIT(dev))
|
|
return false;
|
|
|
|
list_for_each_entry(encoder, &dev->mode_config.encoder_list, base.head) {
|
|
if (encoder->new_crtc != crtc)
|
|
continue;
|
|
|
|
count_hdmi += encoder->type == INTEL_OUTPUT_HDMI;
|
|
count++;
|
|
}
|
|
|
|
/*
|
|
* HDMI 12bpc affects the clocks, so it's only possible
|
|
* when not cloning with other encoder types.
|
|
*/
|
|
return count_hdmi > 0 && count_hdmi == count;
|
|
}
|
|
|
|
bool intel_hdmi_compute_config(struct intel_encoder *encoder,
|
|
struct intel_crtc_config *pipe_config)
|
|
{
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(&encoder->base);
|
|
struct drm_device *dev = encoder->base.dev;
|
|
struct drm_display_mode *adjusted_mode = &pipe_config->adjusted_mode;
|
|
int clock_12bpc = pipe_config->adjusted_mode.crtc_clock * 3 / 2;
|
|
int portclock_limit = hdmi_portclock_limit(intel_hdmi, false);
|
|
int desired_bpp;
|
|
|
|
if (intel_hdmi->color_range_auto) {
|
|
/* See CEA-861-E - 5.1 Default Encoding Parameters */
|
|
if (intel_hdmi->has_hdmi_sink &&
|
|
drm_match_cea_mode(adjusted_mode) > 1)
|
|
intel_hdmi->color_range = HDMI_COLOR_RANGE_16_235;
|
|
else
|
|
intel_hdmi->color_range = 0;
|
|
}
|
|
|
|
if (intel_hdmi->color_range)
|
|
pipe_config->limited_color_range = true;
|
|
|
|
if (HAS_PCH_SPLIT(dev) && !HAS_DDI(dev))
|
|
pipe_config->has_pch_encoder = true;
|
|
|
|
/*
|
|
* HDMI is either 12 or 8, so if the display lets 10bpc sneak
|
|
* through, clamp it down. Note that g4x/vlv don't support 12bpc hdmi
|
|
* outputs. We also need to check that the higher clock still fits
|
|
* within limits.
|
|
*/
|
|
if (pipe_config->pipe_bpp > 8*3 && intel_hdmi->has_hdmi_sink &&
|
|
clock_12bpc <= portclock_limit &&
|
|
hdmi_12bpc_possible(encoder->new_crtc)) {
|
|
DRM_DEBUG_KMS("picking bpc to 12 for HDMI output\n");
|
|
desired_bpp = 12*3;
|
|
|
|
/* Need to adjust the port link by 1.5x for 12bpc. */
|
|
pipe_config->port_clock = clock_12bpc;
|
|
} else {
|
|
DRM_DEBUG_KMS("picking bpc to 8 for HDMI output\n");
|
|
desired_bpp = 8*3;
|
|
}
|
|
|
|
if (!pipe_config->bw_constrained) {
|
|
DRM_DEBUG_KMS("forcing pipe bpc to %i for HDMI\n", desired_bpp);
|
|
pipe_config->pipe_bpp = desired_bpp;
|
|
}
|
|
|
|
if (adjusted_mode->crtc_clock > portclock_limit) {
|
|
DRM_DEBUG_KMS("too high HDMI clock, rejecting mode\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static enum drm_connector_status
|
|
intel_hdmi_detect(struct drm_connector *connector, bool force)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct intel_hdmi *intel_hdmi = intel_attached_hdmi(connector);
|
|
struct intel_digital_port *intel_dig_port =
|
|
hdmi_to_dig_port(intel_hdmi);
|
|
struct intel_encoder *intel_encoder = &intel_dig_port->base;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
struct edid *edid;
|
|
enum intel_display_power_domain power_domain;
|
|
enum drm_connector_status status = connector_status_disconnected;
|
|
|
|
DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n",
|
|
connector->base.id, drm_get_connector_name(connector));
|
|
|
|
power_domain = intel_display_port_power_domain(intel_encoder);
|
|
intel_display_power_get(dev_priv, power_domain);
|
|
|
|
intel_hdmi->has_hdmi_sink = false;
|
|
intel_hdmi->has_audio = false;
|
|
intel_hdmi->rgb_quant_range_selectable = false;
|
|
edid = drm_get_edid(connector,
|
|
intel_gmbus_get_adapter(dev_priv,
|
|
intel_hdmi->ddc_bus));
|
|
|
|
if (edid) {
|
|
if (edid->input & DRM_EDID_INPUT_DIGITAL) {
|
|
status = connector_status_connected;
|
|
if (intel_hdmi->force_audio != HDMI_AUDIO_OFF_DVI)
|
|
intel_hdmi->has_hdmi_sink =
|
|
drm_detect_hdmi_monitor(edid);
|
|
intel_hdmi->has_audio = drm_detect_monitor_audio(edid);
|
|
intel_hdmi->rgb_quant_range_selectable =
|
|
drm_rgb_quant_range_selectable(edid);
|
|
}
|
|
kfree(edid);
|
|
}
|
|
|
|
if (status == connector_status_connected) {
|
|
if (intel_hdmi->force_audio != HDMI_AUDIO_AUTO)
|
|
intel_hdmi->has_audio =
|
|
(intel_hdmi->force_audio == HDMI_AUDIO_ON);
|
|
intel_encoder->type = INTEL_OUTPUT_HDMI;
|
|
}
|
|
|
|
intel_display_power_put(dev_priv, power_domain);
|
|
|
|
return status;
|
|
}
|
|
|
|
static int intel_hdmi_get_modes(struct drm_connector *connector)
|
|
{
|
|
struct intel_encoder *intel_encoder = intel_attached_encoder(connector);
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(&intel_encoder->base);
|
|
struct drm_i915_private *dev_priv = connector->dev->dev_private;
|
|
enum intel_display_power_domain power_domain;
|
|
int ret;
|
|
|
|
/* We should parse the EDID data and find out if it's an HDMI sink so
|
|
* we can send audio to it.
|
|
*/
|
|
|
|
power_domain = intel_display_port_power_domain(intel_encoder);
|
|
intel_display_power_get(dev_priv, power_domain);
|
|
|
|
ret = intel_ddc_get_modes(connector,
|
|
intel_gmbus_get_adapter(dev_priv,
|
|
intel_hdmi->ddc_bus));
|
|
|
|
intel_display_power_put(dev_priv, power_domain);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool
|
|
intel_hdmi_detect_audio(struct drm_connector *connector)
|
|
{
|
|
struct intel_encoder *intel_encoder = intel_attached_encoder(connector);
|
|
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(&intel_encoder->base);
|
|
struct drm_i915_private *dev_priv = connector->dev->dev_private;
|
|
enum intel_display_power_domain power_domain;
|
|
struct edid *edid;
|
|
bool has_audio = false;
|
|
|
|
power_domain = intel_display_port_power_domain(intel_encoder);
|
|
intel_display_power_get(dev_priv, power_domain);
|
|
|
|
edid = drm_get_edid(connector,
|
|
intel_gmbus_get_adapter(dev_priv,
|
|
intel_hdmi->ddc_bus));
|
|
if (edid) {
|
|
if (edid->input & DRM_EDID_INPUT_DIGITAL)
|
|
has_audio = drm_detect_monitor_audio(edid);
|
|
kfree(edid);
|
|
}
|
|
|
|
intel_display_power_put(dev_priv, power_domain);
|
|
|
|
return has_audio;
|
|
}
|
|
|
|
static int
|
|
intel_hdmi_set_property(struct drm_connector *connector,
|
|
struct drm_property *property,
|
|
uint64_t val)
|
|
{
|
|
struct intel_hdmi *intel_hdmi = intel_attached_hdmi(connector);
|
|
struct intel_digital_port *intel_dig_port =
|
|
hdmi_to_dig_port(intel_hdmi);
|
|
struct drm_i915_private *dev_priv = connector->dev->dev_private;
|
|
int ret;
|
|
|
|
ret = drm_object_property_set_value(&connector->base, property, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (property == dev_priv->force_audio_property) {
|
|
enum hdmi_force_audio i = val;
|
|
bool has_audio;
|
|
|
|
if (i == intel_hdmi->force_audio)
|
|
return 0;
|
|
|
|
intel_hdmi->force_audio = i;
|
|
|
|
if (i == HDMI_AUDIO_AUTO)
|
|
has_audio = intel_hdmi_detect_audio(connector);
|
|
else
|
|
has_audio = (i == HDMI_AUDIO_ON);
|
|
|
|
if (i == HDMI_AUDIO_OFF_DVI)
|
|
intel_hdmi->has_hdmi_sink = 0;
|
|
|
|
intel_hdmi->has_audio = has_audio;
|
|
goto done;
|
|
}
|
|
|
|
if (property == dev_priv->broadcast_rgb_property) {
|
|
bool old_auto = intel_hdmi->color_range_auto;
|
|
uint32_t old_range = intel_hdmi->color_range;
|
|
|
|
switch (val) {
|
|
case INTEL_BROADCAST_RGB_AUTO:
|
|
intel_hdmi->color_range_auto = true;
|
|
break;
|
|
case INTEL_BROADCAST_RGB_FULL:
|
|
intel_hdmi->color_range_auto = false;
|
|
intel_hdmi->color_range = 0;
|
|
break;
|
|
case INTEL_BROADCAST_RGB_LIMITED:
|
|
intel_hdmi->color_range_auto = false;
|
|
intel_hdmi->color_range = HDMI_COLOR_RANGE_16_235;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (old_auto == intel_hdmi->color_range_auto &&
|
|
old_range == intel_hdmi->color_range)
|
|
return 0;
|
|
|
|
goto done;
|
|
}
|
|
|
|
return -EINVAL;
|
|
|
|
done:
|
|
if (intel_dig_port->base.base.crtc)
|
|
intel_crtc_restore_mode(intel_dig_port->base.base.crtc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void vlv_hdmi_pre_enable(struct intel_encoder *encoder)
|
|
{
|
|
struct intel_digital_port *dport = enc_to_dig_port(&encoder->base);
|
|
struct drm_device *dev = encoder->base.dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
struct intel_crtc *intel_crtc =
|
|
to_intel_crtc(encoder->base.crtc);
|
|
enum dpio_channel port = vlv_dport_to_channel(dport);
|
|
int pipe = intel_crtc->pipe;
|
|
u32 val;
|
|
|
|
if (!IS_VALLEYVIEW(dev))
|
|
return;
|
|
|
|
/* Enable clock channels for this port */
|
|
mutex_lock(&dev_priv->dpio_lock);
|
|
val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW8(port));
|
|
val = 0;
|
|
if (pipe)
|
|
val |= (1<<21);
|
|
else
|
|
val &= ~(1<<21);
|
|
val |= 0x001000c4;
|
|
vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW8(port), val);
|
|
|
|
/* HDMI 1.0V-2dB */
|
|
vlv_dpio_write(dev_priv, pipe, VLV_TX_DW5(port), 0);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_TX_DW4(port), 0x2b245f5f);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_TX_DW2(port), 0x5578b83a);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_TX_DW3(port), 0x0c782040);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_TX3_DW4(port), 0x2b247878);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW11(port), 0x00030000);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW9(port), 0x00002000);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_TX_DW5(port), DPIO_TX_OCALINIT_EN);
|
|
|
|
/* Program lane clock */
|
|
vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW14(port), 0x00760018);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW23(port), 0x00400888);
|
|
mutex_unlock(&dev_priv->dpio_lock);
|
|
|
|
intel_enable_hdmi(encoder);
|
|
|
|
vlv_wait_port_ready(dev_priv, dport);
|
|
}
|
|
|
|
static void vlv_hdmi_pre_pll_enable(struct intel_encoder *encoder)
|
|
{
|
|
struct intel_digital_port *dport = enc_to_dig_port(&encoder->base);
|
|
struct drm_device *dev = encoder->base.dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
struct intel_crtc *intel_crtc =
|
|
to_intel_crtc(encoder->base.crtc);
|
|
enum dpio_channel port = vlv_dport_to_channel(dport);
|
|
int pipe = intel_crtc->pipe;
|
|
|
|
if (!IS_VALLEYVIEW(dev))
|
|
return;
|
|
|
|
/* Program Tx lane resets to default */
|
|
mutex_lock(&dev_priv->dpio_lock);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW0(port),
|
|
DPIO_PCS_TX_LANE2_RESET |
|
|
DPIO_PCS_TX_LANE1_RESET);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW1(port),
|
|
DPIO_PCS_CLK_CRI_RXEB_EIOS_EN |
|
|
DPIO_PCS_CLK_CRI_RXDIGFILTSG_EN |
|
|
(1<<DPIO_PCS_CLK_DATAWIDTH_SHIFT) |
|
|
DPIO_PCS_CLK_SOFT_RESET);
|
|
|
|
/* Fix up inter-pair skew failure */
|
|
vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW12(port), 0x00750f00);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_TX_DW11(port), 0x00001500);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_TX_DW14(port), 0x40400000);
|
|
|
|
vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW9(port), 0x00002000);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_TX_DW5(port), DPIO_TX_OCALINIT_EN);
|
|
mutex_unlock(&dev_priv->dpio_lock);
|
|
}
|
|
|
|
static void vlv_hdmi_post_disable(struct intel_encoder *encoder)
|
|
{
|
|
struct intel_digital_port *dport = enc_to_dig_port(&encoder->base);
|
|
struct drm_i915_private *dev_priv = encoder->base.dev->dev_private;
|
|
struct intel_crtc *intel_crtc =
|
|
to_intel_crtc(encoder->base.crtc);
|
|
enum dpio_channel port = vlv_dport_to_channel(dport);
|
|
int pipe = intel_crtc->pipe;
|
|
|
|
/* Reset lanes to avoid HDMI flicker (VLV w/a) */
|
|
mutex_lock(&dev_priv->dpio_lock);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW0(port), 0x00000000);
|
|
vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW1(port), 0x00e00060);
|
|
mutex_unlock(&dev_priv->dpio_lock);
|
|
}
|
|
|
|
static void intel_hdmi_destroy(struct drm_connector *connector)
|
|
{
|
|
drm_connector_cleanup(connector);
|
|
kfree(connector);
|
|
}
|
|
|
|
static const struct drm_connector_funcs intel_hdmi_connector_funcs = {
|
|
.dpms = intel_connector_dpms,
|
|
.detect = intel_hdmi_detect,
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.set_property = intel_hdmi_set_property,
|
|
.destroy = intel_hdmi_destroy,
|
|
};
|
|
|
|
static const struct drm_connector_helper_funcs intel_hdmi_connector_helper_funcs = {
|
|
.get_modes = intel_hdmi_get_modes,
|
|
.mode_valid = intel_hdmi_mode_valid,
|
|
.best_encoder = intel_best_encoder,
|
|
};
|
|
|
|
static const struct drm_encoder_funcs intel_hdmi_enc_funcs = {
|
|
.destroy = intel_encoder_destroy,
|
|
};
|
|
|
|
static void
|
|
intel_hdmi_add_properties(struct intel_hdmi *intel_hdmi, struct drm_connector *connector)
|
|
{
|
|
intel_attach_force_audio_property(connector);
|
|
intel_attach_broadcast_rgb_property(connector);
|
|
intel_hdmi->color_range_auto = true;
|
|
}
|
|
|
|
void intel_hdmi_init_connector(struct intel_digital_port *intel_dig_port,
|
|
struct intel_connector *intel_connector)
|
|
{
|
|
struct drm_connector *connector = &intel_connector->base;
|
|
struct intel_hdmi *intel_hdmi = &intel_dig_port->hdmi;
|
|
struct intel_encoder *intel_encoder = &intel_dig_port->base;
|
|
struct drm_device *dev = intel_encoder->base.dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
enum port port = intel_dig_port->port;
|
|
|
|
drm_connector_init(dev, connector, &intel_hdmi_connector_funcs,
|
|
DRM_MODE_CONNECTOR_HDMIA);
|
|
drm_connector_helper_add(connector, &intel_hdmi_connector_helper_funcs);
|
|
|
|
connector->interlace_allowed = 1;
|
|
connector->doublescan_allowed = 0;
|
|
connector->stereo_allowed = 1;
|
|
|
|
switch (port) {
|
|
case PORT_B:
|
|
intel_hdmi->ddc_bus = GMBUS_PORT_DPB;
|
|
intel_encoder->hpd_pin = HPD_PORT_B;
|
|
break;
|
|
case PORT_C:
|
|
intel_hdmi->ddc_bus = GMBUS_PORT_DPC;
|
|
intel_encoder->hpd_pin = HPD_PORT_C;
|
|
break;
|
|
case PORT_D:
|
|
intel_hdmi->ddc_bus = GMBUS_PORT_DPD;
|
|
intel_encoder->hpd_pin = HPD_PORT_D;
|
|
break;
|
|
case PORT_A:
|
|
intel_encoder->hpd_pin = HPD_PORT_A;
|
|
/* Internal port only for eDP. */
|
|
default:
|
|
BUG();
|
|
}
|
|
|
|
if (IS_VALLEYVIEW(dev)) {
|
|
intel_hdmi->write_infoframe = vlv_write_infoframe;
|
|
intel_hdmi->set_infoframes = vlv_set_infoframes;
|
|
} else if (!HAS_PCH_SPLIT(dev)) {
|
|
intel_hdmi->write_infoframe = g4x_write_infoframe;
|
|
intel_hdmi->set_infoframes = g4x_set_infoframes;
|
|
} else if (HAS_DDI(dev)) {
|
|
intel_hdmi->write_infoframe = hsw_write_infoframe;
|
|
intel_hdmi->set_infoframes = hsw_set_infoframes;
|
|
} else if (HAS_PCH_IBX(dev)) {
|
|
intel_hdmi->write_infoframe = ibx_write_infoframe;
|
|
intel_hdmi->set_infoframes = ibx_set_infoframes;
|
|
} else {
|
|
intel_hdmi->write_infoframe = cpt_write_infoframe;
|
|
intel_hdmi->set_infoframes = cpt_set_infoframes;
|
|
}
|
|
|
|
if (HAS_DDI(dev))
|
|
intel_connector->get_hw_state = intel_ddi_connector_get_hw_state;
|
|
else
|
|
intel_connector->get_hw_state = intel_connector_get_hw_state;
|
|
intel_connector->unregister = intel_connector_unregister;
|
|
|
|
intel_hdmi_add_properties(intel_hdmi, connector);
|
|
|
|
intel_connector_attach_encoder(intel_connector, intel_encoder);
|
|
drm_sysfs_connector_add(connector);
|
|
|
|
/* For G4X desktop chip, PEG_BAND_GAP_DATA 3:0 must first be written
|
|
* 0xd. Failure to do so will result in spurious interrupts being
|
|
* generated on the port when a cable is not attached.
|
|
*/
|
|
if (IS_G4X(dev) && !IS_GM45(dev)) {
|
|
u32 temp = I915_READ(PEG_BAND_GAP_DATA);
|
|
I915_WRITE(PEG_BAND_GAP_DATA, (temp & ~0xf) | 0xd);
|
|
}
|
|
}
|
|
|
|
void intel_hdmi_init(struct drm_device *dev, int hdmi_reg, enum port port)
|
|
{
|
|
struct intel_digital_port *intel_dig_port;
|
|
struct intel_encoder *intel_encoder;
|
|
struct intel_connector *intel_connector;
|
|
|
|
intel_dig_port = kzalloc(sizeof(*intel_dig_port), GFP_KERNEL);
|
|
if (!intel_dig_port)
|
|
return;
|
|
|
|
intel_connector = kzalloc(sizeof(*intel_connector), GFP_KERNEL);
|
|
if (!intel_connector) {
|
|
kfree(intel_dig_port);
|
|
return;
|
|
}
|
|
|
|
intel_encoder = &intel_dig_port->base;
|
|
|
|
drm_encoder_init(dev, &intel_encoder->base, &intel_hdmi_enc_funcs,
|
|
DRM_MODE_ENCODER_TMDS);
|
|
|
|
intel_encoder->compute_config = intel_hdmi_compute_config;
|
|
intel_encoder->mode_set = intel_hdmi_mode_set;
|
|
intel_encoder->disable = intel_disable_hdmi;
|
|
intel_encoder->get_hw_state = intel_hdmi_get_hw_state;
|
|
intel_encoder->get_config = intel_hdmi_get_config;
|
|
if (IS_VALLEYVIEW(dev)) {
|
|
intel_encoder->pre_pll_enable = vlv_hdmi_pre_pll_enable;
|
|
intel_encoder->pre_enable = vlv_hdmi_pre_enable;
|
|
intel_encoder->enable = vlv_enable_hdmi;
|
|
intel_encoder->post_disable = vlv_hdmi_post_disable;
|
|
} else {
|
|
intel_encoder->enable = intel_enable_hdmi;
|
|
}
|
|
|
|
intel_encoder->type = INTEL_OUTPUT_HDMI;
|
|
intel_encoder->crtc_mask = (1 << 0) | (1 << 1) | (1 << 2);
|
|
intel_encoder->cloneable = 1 << INTEL_OUTPUT_ANALOG;
|
|
/*
|
|
* BSpec is unclear about HDMI+HDMI cloning on g4x, but it seems
|
|
* to work on real hardware. And since g4x can send infoframes to
|
|
* only one port anyway, nothing is lost by allowing it.
|
|
*/
|
|
if (IS_G4X(dev))
|
|
intel_encoder->cloneable |= 1 << INTEL_OUTPUT_HDMI;
|
|
|
|
intel_dig_port->port = port;
|
|
intel_dig_port->hdmi.hdmi_reg = hdmi_reg;
|
|
intel_dig_port->dp.output_reg = 0;
|
|
|
|
intel_hdmi_init_connector(intel_dig_port, intel_connector);
|
|
}
|