Signed-off-by: Keith Packard <kei...@keithp.com> --- drivers/gpu/drm/i915/Makefile | 1 + drivers/gpu/drm/i915/intel_display.c | 13 +- drivers/gpu/drm/i915/intel_dp.c | 840 ++++++++++++++++++++++++++++++++++ drivers/gpu/drm/i915/intel_dp.h | 133 ++++++ drivers/gpu/drm/i915/intel_drv.h | 1 + 5 files changed, 987 insertions(+), 1 deletions(-) create mode 100644 drivers/gpu/drm/i915/intel_dp.c create mode 100644 drivers/gpu/drm/i915/intel_dp.h
diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile index 51c5a05..184b8bf 100644 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@ -13,6 +13,7 @@ i915-y := i915_drv.o i915_dma.o i915_irq.o i915_mem.o \ intel_crt.o \ intel_lvds.o \ intel_bios.o \ + intel_dp.o \ intel_hdmi.o \ intel_sdvo.o \ intel_modes.o \ diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 504a69f..1fc7bb0 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -29,6 +29,7 @@ #include "intel_drv.h" #include "i915_drm.h" #include "i915_drv.h" +#include "intel_dp.h" #include "drm_crtc_helper.h" @@ -1058,7 +1059,7 @@ static int intel_crtc_mode_set(struct drm_crtc *crtc, intel_clock_t clock; u32 dpll = 0, fp = 0, dspcntr, pipeconf; bool ok, is_sdvo = false, is_dvo = false; - bool is_crt = false, is_lvds = false, is_tv = false; + bool is_crt = false, is_lvds = false, is_tv = false, is_dp = false; struct drm_mode_config *mode_config = &dev->mode_config; struct drm_connector *connector; const intel_limit_t *limit; @@ -1091,6 +1092,9 @@ static int intel_crtc_mode_set(struct drm_crtc *crtc, case INTEL_OUTPUT_ANALOG: is_crt = true; break; + case INTEL_OUTPUT_DISPLAYPORT: + is_dp = true; + break; } num_outputs++; @@ -1155,6 +1159,8 @@ static int intel_crtc_mode_set(struct drm_crtc *crtc, dpll |= (sdvo_pixel_multiply - 1) << SDVO_MULTIPLIER_SHIFT_HIRES; } } + if (is_dp) + dpll |= DPLL_DVO_HIGH_SPEED; /* compute bitmask from p1 value */ if (IS_IGD(dev)) @@ -1270,6 +1276,11 @@ static int intel_crtc_mode_set(struct drm_crtc *crtc, I915_READ(LVDS); } + /* The Display Port M/N ratio needs to be set before the DPLL is enabled + */ + if (is_dp) + intel_dp_set_m_n(crtc, mode, adjusted_mode); + I915_WRITE(fp_reg, fp); I915_WRITE(dpll_reg, dpll); I915_READ(dpll_reg); diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c new file mode 100644 index 0000000..5d07f14 --- /dev/null +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -0,0 +1,840 @@ +/* + * Copyright © 2008 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: + * Keith Packard <kei...@keithp.com> + * + */ + +#include <linux/i2c.h> +#include <linux/delay.h> +#include "drmP.h" +#include "drm.h" +#include "drm_crtc.h" +#include "intel_drv.h" +#include "i915_drm.h" +#include "i915_drv.h" +#include "intel_dp_regs.h" + +#undef SDVO_DEBUG + +#define DP_LINK_STATUS_SIZE 6 +#define DP_LINK_CHECK_TIMEOUT (10 * 1000) + +#define DP_LINK_CONFIGURATION_SIZE 9 + +struct intel_dp_priv { + /* I2C over AUX */ + uint16_t i2c_address; + Bool i2c_running; + + /* Output config register */ + uint32_t output_reg; + + /* Desired output config register state */ + uint32_t DP; + /* Desire link config state */ + uint8_t link_configuration[DP_LINK_CONFIGURATION_SIZE]; + + /* Saved output config state */ + uint32_t save_DP; + uint8_t save_link_configuration[DP_LINK_CONFIGURATION_SIZE]; + + /* whether the sink supports audio (currently unset) */ + Bool has_audio; + + /* available link bandwidth */ + uint8_t link_bw; + + /* available lane count */ + uint8_t lane_count; + + /* Device configuration information */ + uint8_t dpcd[4]; +}; + +static void +intel_dp_link_train(xf86OutputPtr output, uint32_t DP, + uint8_t link_configuration[DP_LINK_CONFIGURATION_SIZE]); + +static void +intel_dp_link_down(xf86OutputPtr output, uint32_t DP); + +static int +intel_dp_max_lane_count(xf86OutputPtr output) +{ + struct drm_device *dev = encoder->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_output *intel_output = enc_to_intel_output(encoder); + struct intel_dp_priv *dp_priv = intel_output->dev_priv; + int max_lane_count = 4; + + if (dp_priv->dpcd[0] >= 0x11) { + max_lane_count = dp_priv->dpcd[2] & 0x1f; + switch (max_lane_count) { + case 1: case 2: case 4: + break; + default: + max_lane_count = 4; + } + } + return max_lane_count; +} + +static int +intel_dp_max_link_bw(xf86OutputPtr output) +{ + struct drm_device *dev = encoder->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_output *intel_output = enc_to_intel_output(encoder); + struct intel_dp_priv *dp_priv = intel_output->dev_priv; + int max_link_bw = dp_priv->dpcd[1]; + + switch (max_link_bw) { + case DP_LINK_BW_1_62: + case DP_LINK_BW_2_7: + break; + default: + max_link_bw = DP_LINK_BW_1_62; + break; + } + return max_link_bw; +} + +static int +intel_dp_link_clock(uint8_t link_bw) +{ + if (link_bw == DP_LINK_BW_2_7) + return 270000; + else + return 162000; +} + +/* Return DP link clock rate. + * + * This driver always encodes 3 bytes per pixel (RGB) + */ +static int +intel_dp_link_required(int pixel_clock) +{ + return pixel_clock * 3; +} + +/* AUX channel communication in native and I2C modes */ + +static uint32_t +intel_pack_aux(uint8_t *src, int src_bytes) +{ + int i; + uint32_t v = 0; + + if (src_bytes > 4) + src_bytes = 4; + for (i = 0; i < src_bytes; i++) + v |= ((uint32_t) src[i]) << ((3-i) * 8); + return v; +} + +static void +intel_unpack_aux(uint32_t src, uint8_t *dst, int dst_bytes) +{ + int i; + if (dst_bytes > 4) + dst_bytes = 4; + for (i = 0; i < dst_bytes; i++) + dst[i] = src >> ((3-i) * 8); +} + +static int +intel_dp_aux_ch(struct drm_encoder *encoder, uint32_t output_reg, + uint8_t *send, int send_bytes, + uint8_t *recv, int recv_size) +{ + struct drm_device *dev = encoder->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_output *intel_output = enc_to_intel_output(encoder); + struct intel_dp_priv *dp_priv = intel_output->dev_priv; + uint32_t ch_ctl = output_reg + 0x10; + uint32_t ch_data = ch_ctl + 4; + int i; + int recv_bytes; + uint32_t ctl; + uint32_t status; + + /* Load the send data into the aux channel data registers */ + for (i = 0; i < send_bytes; i += 4) { + uint32_t d = intel_pack_aux(send + i, send_bytes - i);; + + I915_WRITE(ch_data + i, d); + } + + /* The clock divider is based off the hrawclk, + * and would like to run at 2MHz. The 133 below assumes + * a 266MHz hrawclk; need to figure out how we're supposed + * to know what hrawclk is... + */ + ctl = (DP_AUX_CH_CTL_SEND_BUSY | + DP_AUX_CH_CTL_TIME_OUT_1600us | + (send_bytes << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) | + (5 << DP_AUX_CH_CTL_PRECHARGE_2US_SHIFT) | + (133 << DP_AUX_CH_CTL_BIT_CLOCK_2X_SHIFT) | + DP_AUX_CH_CTL_TIME_OUT_ERROR | + DP_AUX_CH_CTL_RECEIVE_ERROR); + + /* Send the command and wait for it to complete */ + I915_WRITE(ch_ctl, ctl); + for (;;) { + status = I915_READ(ch_ctl); + if ((status & DP_AUX_CH_CTL_SEND_BUSY) == 0) + break; + udelay(100); + } + + /* Clear done status and any errors */ + I915_READ(ch_ctl, (ctl | + DP_AUX_CH_CTL_DONE | + DP_AUX_CH_CTL_TIME_OUT_ERROR | + DP_AUX_CH_CTL_RECEIVE_ERROR)); + + if ((status & DP_AUX_CH_CTL_DONE) == 0) + return -EIO; + + /* Check for timeout or receive error. + * Timeouts occur when the sink is not connected + */ + if (status & (DP_AUX_CH_CTL_TIME_OUT_ERROR | DP_AUX_CH_CTL_RECEIVE_ERROR)) + return -EIO; + + /* Unload any bytes sent back from the other side */ + recv_bytes = ((status & DP_AUX_CH_CTL_MESSAGE_SIZE_MASK) >> + DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT); + + if (recv_bytes > recv_size) + recv_bytes = recv_size; + for (i = 0; i < recv_bytes; i += 4) { + uint32_t d = INREG(ch_data + i); + + intel_unpack_aux(d, recv + i, recv_bytes - i); + } + + return recv_bytes; +} + +/* Write data to the aux channel in native mode */ +static int +intel_dp_aux_native_write(struct drm_encoder *encoder, uint32_t output_reg, + uint16_t address, uint8_t *send, int send_bytes) +{ + int ret; + uint8_t msg[20]; + int msg_bytes; + uint8_t ack; + + assert(send_bytes <= 16); + msg[0] = AUX_NATIVE_WRITE << 4; + msg[1] = address >> 8; + msg[2] = address; + msg[3] = send_bytes - 1; + memcpy(&msg[4], send, send_bytes); + msg_bytes = send_bytes + 4; + for (;;) { + ret = intel_dp_aux_ch(encoder, output_reg, msg, msg_bytes, &ack, 1); + if (ret < 0) + return ret; + if ((ack & AUX_NATIVE_REPLY_MASK) == AUX_NATIVE_REPLY_ACK) + break; + else if ((ack & AUX_NATIVE_REPLY_MASK) == AUX_NATIVE_REPLY_DEFER) + udelay(100); + else + return -EIO; + } + return send_bytes; +} + +/* Write a single byte to the aux channel in native mode */ +static int +intel_dp_aux_native_write_1(struct drm_encoder *encoder, uint32_t output_reg, + uint16_t address, uint8_t byte) +{ + return intel_dp_aux_native_write(encoder, output_reg, address, &byte, 1); +} + +/* read bytes from a native aux channel */ +static int +i830_dp_aux_native_read(struct drm_encoder *encoder, uint32_t output_reg, + uint16_t address, uint8_t *recv, int recv_bytes) +{ + uint8_t msg[4]; + int msg_bytes; + uint8_t reply[20]; + int reply_bytes; + uint8_t ack; + int ret; + + msg[0] = AUX_NATIVE_READ << 4; + msg[1] = address >> 8; + msg[2] = address & 0xff; + msg[3] = recv_bytes - 1; + + msg_bytes = 4; + reply_bytes = recv_bytes + 1; + + for (;;) { + ret = intel_dp_aux_ch(encoder, output_reg, msg, msg_bytes, + reply, reply_bytes); + if (ret <= 0) + return ret; + ack = reply[0]; + if ((ack & AUX_NATIVE_REPLY_MASK) == AUX_NATIVE_REPLY_ACK) { + memcpy(recv, reply + 1, ret - 1); + return ret - 1; + } + else if ((ack & AUX_NATIVE_REPLY_MASK) == AUX_NATIVE_REPLY_DEFER) + usleep(100); + else + return -EIO; + } +} + +/* Run a single AUX_CH I2C transaction, writing/reading data as necessary */ + +enum dp_aux_i2c_mode { + aux_i2c_start, + aux_i2c_write, + aux_i2c_read, + aux_i2c_stop +}; + +static int +intel_dp_aux_i2c_transaction(struct drm_encoder *encoder, uint32_t output_reg, + uint16_t address, + enum dp_aux_i2c_mode mode, + uint8_t write_byte, uint8_t *read_byte) +{ + uint8_t msg[5]; + uint8_t reply[2]; + int msg_bytes; + int reply_bytes; + int ret; + + /* Set up the command byte */ + if (address & 1) + msg[0] = AUX_I2C_READ << 4; + else + msg[0] = AUX_I2C_WRITE << 4; + + if (mode != aux_i2c_stop) + msg[0] |= AUX_I2C_MOT << 4; + + /* Note that the AUX_CH I2C stuff wants the read/write + * bit stripped off + */ + msg[1] = address >> 9; + msg[2] = address >> 1; + + switch (mode) { + case aux_i2c_start: + case aux_i2c_stop: + default: + msg_bytes = 3; + reply_bytes = 1; + break; + case aux_i2c_write: + msg[3] = 0; + msg[4] = write_byte; + msg_bytes = 5; + reply_bytes = 1; + break; + case aux_i2c_read: + msg[3] = 0; + msg_bytes = 4; + reply_bytes = 2; + break; + } + + for (;;) { + ret = intel_dp_aux_ch(encoder, output_reg, msg, msg_bytes, + reply, reply_bytes); + if (ret <= 0) + return ret; + + if ((reply[0] & AUX_I2C_REPLY_MASK) == AUX_I2C_REPLY_ACK) { + if (mode == aux_i2c_read) + *read_byte = reply[1]; + return reply_bytes - 1; + } + else if ((reply[0] & AUX_I2C_REPLY_MASK) == AUX_I2C_REPLY_DEFER) + usleep(100); + else + return -EIO; + } +} + +/* + * I2C over AUX CH + */ + +/* + * Send the address. If the I2C link is running, this 'restarts' + * the connection with the new address, this is used for doing + * a write followed by a read (as needed for DDC) + */ +static Bool +i830_dp_i2c_address(I2CDevPtr dev, I2CSlaveAddr addr) +{ + I2CBusPtr bus = dev->pI2CBus; + xf86OutputPtr output = bus->DriverPrivate.ptr; + I830OutputPrivatePtr intel_output = output->driver_private; + struct i830_dp_priv *dev_priv = intel_output->dev_priv; + ScrnInfoPtr scrn = output->scrn; + + dev_priv->i2c_address = addr; + dev_priv->i2c_running = TRUE; + return i830_dp_aux_i2c_transaction(scrn, dev_priv->output_reg, addr, + aux_i2c_start, 0, NULL) >= 0; +} + +/* DIX never even calls this function, so it better not be necessary */ +static Bool +i830_dp_i2c_start(I2CBusPtr bus, int timeout) +{ + return TRUE; +} + +/* + * Stop the I2C transaction. This closes out the link, sending + * a bare address packet with the MOT bit turned off + */ +static void +i830_dp_i2c_stop(I2CDevPtr dev) +{ + I2CBusPtr bus = dev->pI2CBus; + xf86OutputPtr output = bus->DriverPrivate.ptr; + ScrnInfoPtr scrn = output->scrn; + I830OutputPrivatePtr intel_output = output->driver_private; + struct i830_dp_priv *dev_priv = intel_output->dev_priv; + + if (dev_priv->i2c_running) + (void) i830_dp_aux_i2c_transaction(scrn, dev_priv->output_reg, + dev_priv->i2c_address, + aux_i2c_stop, 0, NULL); + dev_priv->i2c_running = FALSE; +} + +/* + * Write a single byte to the current I2C address, this assumes + * that the I2C link is running (or presumably it won't work). + */ +static Bool +i830_dp_i2c_put_byte(I2CDevPtr dev, I2CByte byte) +{ + I2CBusPtr bus = dev->pI2CBus; + xf86OutputPtr output = bus->DriverPrivate.ptr; + ScrnInfoPtr scrn = output->scrn; + I830OutputPrivatePtr intel_output = output->driver_private; + struct i830_dp_priv *dev_priv = intel_output->dev_priv; + + return i830_dp_aux_i2c_transaction(scrn, dev_priv->output_reg, + dev_priv->i2c_address, + aux_i2c_write, byte, NULL) >= 0; +} + +static Bool +i830_dp_i2c_get_byte(I2CDevPtr dev, I2CByte *byte_ret, Bool last) +{ + I2CBusPtr bus = dev->pI2CBus; + xf86OutputPtr output = bus->DriverPrivate.ptr; + I830OutputPrivatePtr intel_output = output->driver_private; + struct i830_dp_priv *dev_priv = intel_output->dev_priv; + ScrnInfoPtr scrn = output->scrn; + + return i830_dp_aux_i2c_transaction(scrn, dev_priv->output_reg, + dev_priv->i2c_address, + aux_i2c_read, 0, byte_ret) == 1; +} + +static Bool +i830_dp_i2c_init(ScrnInfoPtr pScrn, I2CBusPtr *bus_ptr, + xf86OutputPtr output, char *name) +{ + I2CBusPtr pI2CBus; + + pI2CBus = xf86CreateI2CBusRec(); + + if (!pI2CBus) + return FALSE; + + pI2CBus->BusName = name; + pI2CBus->scrnIndex = pScrn->scrnIndex; + pI2CBus->I2CGetByte = i830_dp_i2c_get_byte; + pI2CBus->I2CPutByte = i830_dp_i2c_put_byte; + pI2CBus->I2CAddress = i830_dp_i2c_address; + pI2CBus->I2CStart = i830_dp_i2c_start; + pI2CBus->I2CStop = i830_dp_i2c_stop; + pI2CBus->DriverPrivate.ptr = output; + + /* Assume all busses are used for DDCish stuff */ + + /* + * These were set incorrectly in the server pre-1.3, Having + * duplicate settings is sub-optimal, but this lets the driver + * work with older servers + */ + pI2CBus->ByteTimeout = 2200; /* VESA DDC spec 3 p. 43 (+10 %) */ + pI2CBus->StartTimeout = 550; + pI2CBus->BitTimeout = 40; + pI2CBus->AcknTimeout = 40; + pI2CBus->RiseFallTime = 20; + + if (!xf86I2CBusInit(pI2CBus)) + return FALSE; + + *bus_ptr = pI2CBus; + return TRUE; +} + +static int +intel_dp_mode_valid(xf86OutputPtr output, DisplayModePtr mode) +{ + int max_link_clock = intel_dp_link_clock(intel_dp_max_link_bw(output)); + int max_lanes = intel_dp_max_lane_count(output); + + if (intel_dp_link_required(mode->Clock) > max_link_clock * max_lanes) + return MODE_CLOCK_HIGH; + + if (mode->Clock < 10000) + return MODE_CLOCK_LOW; + + return MODE_OK; +} + +static void intel_dp_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = encoder->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_output *intel_output = enc_to_intel_output(encoder); + struct intel_dp_priv *dp_priv = intel_output->dev_priv; + I830OutputPrivatePtr intel_output = output->driver_private; + struct i830_dp_priv *dev_priv = intel_output->dev_priv; + xf86CrtcPtr crtc = output->crtc; + I830CrtcPrivatePtr intel_crtc = crtc->driver_private; + + dp_priv->DP = (DP_LINK_TRAIN_OFF | + DP_VOLTAGE_0_4 | + DP_PRE_EMPHASIS_0 | + DP_SYNC_VS_HIGH | + DP_SYNC_HS_HIGH); + + switch (dp_priv->lane_count) { + case 1: + dp_priv->DP |= DP_PORT_WIDTH_1; + break; + case 2: + dp_priv->DP |= DP_PORT_WIDTH_2; + break; + case 4: + dp_priv->DP |= DP_PORT_WIDTH_4; + break; + } + if (dp_priv->has_audio) + dp_priv->DP |= DP_AUDIO_OUTPUT_ENABLE; + + memset(dp_priv->link_configuration, 0, DP_LINK_CONFIGURATION_SIZE); + dp_priv->link_configuration[0] = dp_priv->link_bw; + dp_priv->link_configuration[1] = dp_priv->lane_count; + + /* + * Check for DPCD version > 1.1, + * enable enahanced frame stuff in that case + */ + if (dp_priv->dpcd[0] >= 0x11) { + dp_priv->link_configuration[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + dp_priv->DP |= DP_ENHANCED_FRAMING; + } + + if (intel_crtc->pipe == 1) + dp_priv->DP |= DP_PIPEB_SELECT; +} + +static void intel_dp_dpms(struct drm_encoder *encoder, int mode) +{ + struct drm_device *dev = encoder->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_output *intel_output = enc_to_intel_output(encoder); + struct intel_dp_priv *dp_priv = intel_output->dev_priv; + uint32_t dp_reg = I915_READ(dp_priv->output_reg); + + if (mode == DPMSModeOff) { + if (dp_reg & DP_PORT_EN) + i830_dp_link_down(output, dp_priv->DP); + } else { + if (!(dp_reg & DP_PORT_EN)) + i830_dp_link_train(output, dp_priv->DP, dp_priv->link_configuration); + } +} + +static void intel_dp_save(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_output *intel_output = to_intel_output(connector); + struct intel_dp_priv *dp_priv = intel_output->dev_priv; + + dp_priv->save_DP = I915_READ(dp_priv->output_reg); + + /* Save the link configuration */ + intel_dp_aux_native_read(pScrn, dp_priv->output_reg, DP_LINK_BW_SET, + dp_priv->save_link_configuration, + sizeof (dp_priv->save_link_configuration)); + +} + +static void intel_dp_restore(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_output *intel_output = to_intel_output(connector); + struct intel_dp_priv *dp_priv = intel_output->dev_priv; + + I915_WRITE(dp_priv->output_reg, dp_priv->save_DP); +} + +static int intel_dp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + int max_link_clock = i830_dp_link_clock(i830_dp_max_link_bw(output)); + int max_lanes = i830_dp_max_lane_count(output); + + if (intel_dp_link_required(mode->Clock) > max_link_clock * max_lanes) + return MODE_CLOCK_HIGH; + + if (mode->Clock < 10000) + return MODE_CLOCK_LOW; + + return MODE_OK; +} + +static bool intel_dp_mode_fixup(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_output *intel_output = to_intel_output(connector); + struct intel_dp_priv *dp_priv = intel_output->dev_priv; + int lane_count, clock; + int max_lane_count = i830_dp_max_lane_count(output); + int max_clock = i830_dp_max_link_bw(output) == DP_LINK_BW_2_7 ? 1 : 0;; + static int bws[2] = { DP_LINK_BW_1_62, DP_LINK_BW_2_7 }; + + for (lane_count = 1; lane_count <= max_lane_count; lane_count <<= 1) + { + for (clock = 0; clock <= max_clock; clock++) + { + int link_avail = intel_dp_link_clock(bws[clock]) * lane_count; + + if (intel_dp_link_required(mode->Clock) <= link_avail) + { + dev_priv->link_bw = bws[clock]; + dev_priv->lane_count = lane_count; + adjusted_mode->Clock = i830_dp_link_clock(dev_priv->link_bw); + return TRUE; + } + } + } + return FALSE; +} + +static enum drm_connector_status +intel_dp_detect(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_output *intel_output = to_intel_output(connector); + struct intel_dp_priv *dp_priv = intel_output->dev_priv; + u32 temp, bit; + enum drm_connector_status status; + + dp_priv->has_audio = FALSE; + + temp = I915_READ(PORT_HOTPLUG_EN); + + I915_WRITE(PORT_HOTPLUG_EN, + temp | + DPB_HOTPLUG_INT_EN | + DPC_HOTPLUG_INT_EN | + DPD_HOTPLUG_INT_EN); + + POSTING_READ(PORT_HOTPLUG_EN); + + switch (dev_priv->output_reg) { + case DP_B: + bit = DPB_HOTPLUG_INT_STATUS; + break; + case DP_C: + bit = DPC_HOTPLUG_INT_STATUS; + break; + case DP_D: + bit = DPD_HOTPLUG_INT_STATUS; + break; + default: + return connector_status_unknown; + } + + if ((I915_READ(PORT_HOTPLUG_STAT) & bit) == 0) + return connector_status_disconnected; + + status = connector_status_disconnected; + if (intel_dp_aux_native_read(pScrn, dp_priv->output_reg, + 0, dp_priv->dpcd, + sizeof (dp_priv->dpcd)) == sizeof (dp_priv->dpcd)) + { + if (dp_priv->dpcd[0] != 0) + status = connector_status_connected; + } + return status; +} + +static int intel_dp_get_modes(struct drm_connector *connector) +{ + struct intel_output *intel_output = to_intel_output(connector); + + /* We should parse the EDID data and find out if it's an HDMI sink so + * we can send audio to it. + */ + + return intel_ddc_get_modes(intel_output); +} + +static void intel_dp_destroy(struct drm_connector *connector) +{ + struct intel_output *intel_output = to_intel_output(connector); + + if (intel_output->i2c_bus) + intel_i2c_destroy(intel_output->i2c_bus); + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(intel_output); +} + +static const struct drm_encoder_helper_funcs intel_dp_helper_funcs = { + .dpms = intel_dp_dpms, + .mode_fixup = intel_dp_mode_fixup, + .prepare = intel_encoder_prepare, + .mode_set = intel_dp_mode_set, + .commit = intel_encoder_commit, +}; + +static const struct drm_connector_funcs intel_dp_connector_funcs = { + .save = intel_dp_save, + .restore = intel_dp_restore, + .detect = intel_dp_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = intel_dp_destroy, +}; + +static const struct drm_connector_helper_funcs intel_dp_connector_helper_funcs = { + .get_modes = intel_dp_get_modes, + .mode_valid = intel_dp_mode_valid, + .best_encoder = intel_best_encoder, +}; + +static void intel_dp_enc_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs intel_dp_enc_funcs = { + .destroy = intel_dp_enc_destroy, +}; + +struct intel_i2c_chan *intel_dp_i2c_create(struct drm_device *dev, const char *name) +{ +} + +void intel_dp_init(struct drm_device *dev, int output_reg) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct drm_connector *connector; + struct intel_output *intel_output; + struct intel_dp_priv *dp_priv; + + intel_output = kcalloc(sizeof(struct intel_output) + + sizeof(struct intel_dp_priv), 1, GFP_KERNEL); + if (!intel_output) + return; + dp_priv = (struct intel_dp_priv *)(intel_output + 1); + + connector = &intel_output->base; + drm_connector_init(dev, connector, &intel_dp_connector_funcs, + DRM_MODE_CONNECTOR_DVID); + drm_connector_helper_add(connector, &intel_dp_connector_helper_funcs); + + intel_output->type = INTEL_OUTPUT_DISPLAYPORT; + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + /* Set up the DDC bus. */ + switch (output_reg) { + case DP_B: + default: + intel_output->ddc_bus = intel_i2c_create(dev, "DP_B"); + case DP_C: + intel_output->ddc_bus = intel_i2c_create(dev, "DP_C"); + case DP_D: + intel_output->ddc_bus = intel_i2c_create(dev, "DP_D"); + } + + if (!intel_output->ddc_bus) + goto err_connector; + + dp_priv->output_reg = output_reg; + intel_output->dev_priv = dp_priv; + + drm_encoder_init(dev, &intel_output->enc, &intel_dp_enc_funcs, + DRM_MODE_ENCODER_TMDS); + drm_encoder_helper_add(&intel_output->enc, &intel_dp_helper_funcs); + + drm_mode_connector_attach_encoder(&intel_output->base, + &intel_output->enc); + 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); + } + + return; + +err_connector: + drm_connector_cleanup(connector); + kfree(intel_output); + + return; +} diff --git a/drivers/gpu/drm/i915/intel_dp.h b/drivers/gpu/drm/i915/intel_dp.h new file mode 100644 index 0000000..cfce2c5 --- /dev/null +++ b/drivers/gpu/drm/i915/intel_dp.h @@ -0,0 +1,133 @@ +/* + * Copyright © 2008 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef _INTEL_DP_H_ +#define _INTEL_DP_H_ + +/* From the VESA DisplayPort spec */ + +#define AUX_NATIVE_WRITE 0x8 +#define AUX_NATIVE_READ 0x9 +#define AUX_I2C_WRITE 0x0 +#define AUX_I2C_READ 0x1 +#define AUX_I2C_STATUS 0x2 +#define AUX_I2C_MOT 0x4 + +#define AUX_NATIVE_REPLY_ACK 0x0 +#define AUX_NATIVE_REPLY_NACK 0x1 +#define AUX_NATIVE_REPLY_DEFER 0x2 +#define AUX_NATIVE_REPLY_MASK 0x3 + +#define AUX_I2C_REPLY_ACK (0x0 << 2) +#define AUX_I2C_REPLY_NACK (0x1 << 2) +#define AUX_I2C_REPLY_DEFER (0x2 << 2) +#define AUX_I2C_REPLY_MASK (0x3 << 2) + +/* AUX CH addresses */ +#define DP_LINK_BW_SET 0x100 +# define DP_LINK_BW_1_62 0x06 +# define DP_LINK_BW_2_7 0x0a + +#define DP_LANE_COUNT_SET 0x101 +# define DP_LANE_COUNT_MASK 0x0f +# define DP_LANE_COUNT_ENHANCED_FRAME_EN (1 << 7) + +#define DP_TRAINING_PATTERN_SET 0x102 + +# define DP_TRAINING_PATTERN_DISABLE 0 +# define DP_TRAINING_PATTERN_1 1 +# define DP_TRAINING_PATTERN_2 2 +# define DP_TRAINING_PATTERN_MASK 0x3 + +# define DP_LINK_QUAL_PATTERN_DISABLE (0 << 2) +# define DP_LINK_QUAL_PATTERN_D10_2 (1 << 2) +# define DP_LINK_QUAL_PATTERN_ERROR_RATE (2 << 2) +# define DP_LINK_QUAL_PATTERN_PRBS7 (3 << 2) +# define DP_LINK_QUAL_PATTERN_MASK (3 << 2) + +# define DP_RECOVERED_CLOCK_OUT_EN (1 << 4) +# define DP_LINK_SCRAMBLING_DISABLE (1 << 5) + +# define DP_SYMBOL_ERROR_COUNT_BOTH (0 << 6) +# define DP_SYMBOL_ERROR_COUNT_DISPARITY (1 << 6) +# define DP_SYMBOL_ERROR_COUNT_SYMBOL (2 << 6) +# define DP_SYMBOL_ERROR_COUNT_MASK (3 << 6) + +#define DP_TRAINING_LANE0_SET 0x103 +#define DP_TRAINING_LANE1_SET 0x104 +#define DP_TRAINING_LANE2_SET 0x105 +#define DP_TRAINING_LANE3_SET 0x106 + +# define DP_TRAIN_VOLTAGE_SWING_MASK 0x3 +# define DP_TRAIN_VOLTAGE_SWING_SHIFT 0 +# define DP_TRAIN_MAX_SWING_REACHED (1 << 2) +# define DP_TRAIN_VOLTAGE_SWING_400 (0 << 0) +# define DP_TRAIN_VOLTAGE_SWING_600 (1 << 0) +# define DP_TRAIN_VOLTAGE_SWING_800 (2 << 0) +# define DP_TRAIN_VOLTAGE_SWING_1200 (3 << 0) + +# define DP_TRAIN_PRE_EMPHASIS_MASK (3 << 3) +# define DP_TRAIN_PRE_EMPHASIS_0 (0 << 3) +# define DP_TRAIN_PRE_EMPHASIS_3_5 (1 << 3) +# define DP_TRAIN_PRE_EMPHASIS_6 (2 << 3) +# define DP_TRAIN_PRE_EMPHASIS_9_5 (3 << 3) + +# define DP_TRAIN_PRE_EMPHASIS_SHIFT 3 +# define DP_TRAIN_MAX_PRE_EMPHASIS_REACHED (1 << 5) + +#define DP_DOWNSPREAD_CTRL 0x107 +# define DP_SPREAD_AMP_0_5 (1 << 4) + +#define DP_MAIN_LINK_CHANNEL_CODING_SET 0x108 +# define DP_SET_ANSI_8B10B (1 << 0) + +#define DP_LANE0_1_STATUS 0x202 +#define DP_LANE2_3_STATUS 0x203 + +# define DP_LANE_CR_DONE (1 << 0) +# define DP_LANE_CHANNEL_EQ_DONE (1 << 1) +# define DP_LANE_SYMBOL_LOCKED (1 << 2) + +#define DP_LANE_ALIGN_STATUS_UPDATED 0x204 + +#define DP_INTERLANE_ALIGN_DONE (1 << 0) +#define DP_DOWNSTREAM_PORT_STATUS_CHANGED (1 << 6) +#define DP_LINK_STATUS_UPDATED (1 << 7) + +#define DP_SINK_STATUS 0x205 + +#define DP_RECEIVE_PORT_0_STATUS (1 << 0) +#define DP_RECEIVE_PORT_1_STATUS (1 << 1) + +#define DP_ADJUST_REQUEST_LANE0_1 0x206 +#define DP_ADJUST_REQUEST_LANE2_3 0x207 + +#define DP_ADJUST_VOLTAGE_SWING_LANE0_MASK 0x03 +#define DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT 0 +#define DP_ADJUST_PRE_EMPHASIS_LANE0_MASK 0x0c +#define DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT 2 +#define DP_ADJUST_VOLTAGE_SWING_LANE1_MASK 0x30 +#define DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT 4 +#define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK 0xc0 +#define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT 6 + +#endif /* _INTEL_DP_H_ */ diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index c585879..0937112 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -54,6 +54,7 @@ #define INTEL_OUTPUT_LVDS 4 #define INTEL_OUTPUT_TVOUT 5 #define INTEL_OUTPUT_HDMI 6 +#define INTEL_OUTPUT_DISPLAYPORT 7 #define INTEL_DVO_CHIP_NONE 0 #define INTEL_DVO_CHIP_LVDS 1 -- 1.6.3.1
------------------------------------------------------------------------------ Register Now for Creativity and Technology (CaT), June 3rd, NYC. CaT is a gathering of tech-side developers & brand creativity professionals. Meet the minds behind Google Creative Lab, Visual Complexity, Processing, & iPhoneDevCamp as they present alongside digital heavyweights like Barbarian Group, R/GA, & Big Spaceship. http://p.sf.net/sfu/creativitycat-com
-- _______________________________________________ Dri-devel mailing list Dri-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/dri-devel