diff options
Diffstat (limited to 'drivers/gpu/drm/i915/intel_sdvo.c')
-rw-r--r-- | drivers/gpu/drm/i915/intel_sdvo.c | 870 |
1 files changed, 772 insertions, 98 deletions
diff --git a/drivers/gpu/drm/i915/intel_sdvo.c b/drivers/gpu/drm/i915/intel_sdvo.c index 40721546910..a30508b639b 100644 --- a/drivers/gpu/drm/i915/intel_sdvo.c +++ b/drivers/gpu/drm/i915/intel_sdvo.c @@ -40,13 +40,59 @@ struct intel_sdvo_priv { struct intel_i2c_chan *i2c_bus; int slaveaddr; + + /* Register for the SDVO device: SDVOB or SDVOC */ int output_device; - u16 active_outputs; + /* Active outputs controlled by this SDVO output */ + uint16_t controlled_output; + /* + * Capabilities of the SDVO device returned by + * i830_sdvo_get_capabilities() + */ struct intel_sdvo_caps caps; + + /* Pixel clock limitations reported by the SDVO device, in kHz */ int pixel_clock_min, pixel_clock_max; + /** + * This is set if we're going to treat the device as TV-out. + * + * While we have these nice friendly flags for output types that ought + * to decide this for us, the S-Video output on our HDMI+S-Video card + * shows up as RGB1 (VGA). + */ + bool is_tv; + + /** + * This is set if we treat the device as HDMI, instead of DVI. + */ + bool is_hdmi; + + /** + * Returned SDTV resolutions allowed for the current format, if the + * device reported it. + */ + struct intel_sdvo_sdtv_resolution_reply sdtv_resolutions; + + /** + * Current selected TV format. + * + * This is stored in the same structure that's passed to the device, for + * convenience. + */ + struct intel_sdvo_tv_format tv_format; + + /* + * supported encoding mode, used to determine whether HDMI is + * supported + */ + struct intel_sdvo_encode encode; + + /* DDC bus used by this SDVO output */ + uint8_t ddc_bus; + int save_sdvo_mult; u16 save_active_outputs; struct intel_sdvo_dtd save_input_dtd_1, save_input_dtd_2; @@ -148,8 +194,8 @@ static bool intel_sdvo_write_byte(struct intel_output *intel_output, int addr, #define SDVO_CMD_NAME_ENTRY(cmd) {cmd, #cmd} /** Mapping of command numbers to names, for debug output */ const static struct _sdvo_cmd_name { - u8 cmd; - char *name; + u8 cmd; + char *name; } sdvo_cmd_names[] = { SDVO_CMD_NAME_ENTRY(SDVO_CMD_RESET), SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_DEVICE_CAPS), @@ -186,8 +232,35 @@ const static struct _sdvo_cmd_name { SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SUPPORTED_TV_FORMATS), SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_TV_FORMAT), SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_TV_FORMAT), - SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_TV_RESOLUTION_SUPPORT), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SUPPORTED_POWER_STATES), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_POWER_STATE), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_ENCODER_POWER_STATE), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_DISPLAY_POWER_STATE), SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_CONTROL_BUS_SWITCH), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SDTV_RESOLUTION_SUPPORT), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SCALED_HDTV_RESOLUTION_SUPPORT), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SUPPORTED_ENHANCEMENTS), + /* HDMI op code */ + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SUPP_ENCODE), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_ENCODE), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_ENCODE), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_PIXEL_REPLI), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_PIXEL_REPLI), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_COLORIMETRY_CAP), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_COLORIMETRY), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_COLORIMETRY), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_AUDIO_ENCRYPT_PREFER), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_AUDIO_STAT), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_AUDIO_STAT), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HBUF_INDEX), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_HBUF_INDEX), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HBUF_INFO), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HBUF_AV_SPLIT), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_HBUF_AV_SPLIT), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HBUF_TXRATE), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_HBUF_TXRATE), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_HBUF_DATA), + SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HBUF_DATA), }; #define SDVO_NAME(dev_priv) ((dev_priv)->output_device == SDVOB ? "SDVOB" : "SDVOC") @@ -506,6 +579,50 @@ static bool intel_sdvo_set_output_timing(struct intel_output *intel_output, SDVO_CMD_SET_OUTPUT_TIMINGS_PART1, dtd); } +static bool +intel_sdvo_create_preferred_input_timing(struct intel_output *output, + uint16_t clock, + uint16_t width, + uint16_t height) +{ + struct intel_sdvo_preferred_input_timing_args args; + uint8_t status; + + args.clock = clock; + args.width = width; + args.height = height; + intel_sdvo_write_cmd(output, SDVO_CMD_CREATE_PREFERRED_INPUT_TIMING, + &args, sizeof(args)); + status = intel_sdvo_read_response(output, NULL, 0); + if (status != SDVO_CMD_STATUS_SUCCESS) + return false; + + return true; +} + +static bool intel_sdvo_get_preferred_input_timing(struct intel_output *output, + struct intel_sdvo_dtd *dtd) +{ + bool status; + + intel_sdvo_write_cmd(output, SDVO_CMD_GET_PREFERRED_INPUT_TIMING_PART1, + NULL, 0); + + status = intel_sdvo_read_response(output, &dtd->part1, + sizeof(dtd->part1)); + if (status != SDVO_CMD_STATUS_SUCCESS) + return false; + + intel_sdvo_write_cmd(output, SDVO_CMD_GET_PREFERRED_INPUT_TIMING_PART2, + NULL, 0); + + status = intel_sdvo_read_response(output, &dtd->part2, + sizeof(dtd->part2)); + if (status != SDVO_CMD_STATUS_SUCCESS) + return false; + + return false; +} static int intel_sdvo_get_clock_rate_mult(struct intel_output *intel_output) { @@ -536,36 +653,12 @@ static bool intel_sdvo_set_clock_rate_mult(struct intel_output *intel_output, u8 return true; } -static bool intel_sdvo_mode_fixup(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) -{ - /* Make the CRTC code factor in the SDVO pixel multiplier. The SDVO - * device will be told of the multiplier during mode_set. - */ - adjusted_mode->clock *= intel_sdvo_get_pixel_multiplier(mode); - return true; -} - -static void intel_sdvo_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) +static void intel_sdvo_get_dtd_from_mode(struct intel_sdvo_dtd *dtd, + struct drm_display_mode *mode) { - struct drm_device *dev = encoder->dev; - struct drm_i915_private *dev_priv = dev->dev_private; - struct drm_crtc *crtc = encoder->crtc; - struct intel_crtc *intel_crtc = to_intel_crtc(crtc); - struct intel_output *intel_output = enc_to_intel_output(encoder); - struct intel_sdvo_priv *sdvo_priv = intel_output->dev_priv; - u16 width, height; - u16 h_blank_len, h_sync_len, v_blank_len, v_sync_len; - u16 h_sync_offset, v_sync_offset; - u32 sdvox; - struct intel_sdvo_dtd output_dtd; - int sdvo_pixel_multiply; - - if (!mode) - return; + uint16_t width, height; + uint16_t h_blank_len, h_sync_len, v_blank_len, v_sync_len; + uint16_t h_sync_offset, v_sync_offset; width = mode->crtc_hdisplay; height = mode->crtc_vdisplay; @@ -580,93 +673,423 @@ static void intel_sdvo_mode_set(struct drm_encoder *encoder, h_sync_offset = mode->crtc_hsync_start - mode->crtc_hblank_start; v_sync_offset = mode->crtc_vsync_start - mode->crtc_vblank_start; - output_dtd.part1.clock = mode->clock / 10; - output_dtd.part1.h_active = width & 0xff; - output_dtd.part1.h_blank = h_blank_len & 0xff; - output_dtd.part1.h_high = (((width >> 8) & 0xf) << 4) | + dtd->part1.clock = mode->clock / 10; + dtd->part1.h_active = width & 0xff; + dtd->part1.h_blank = h_blank_len & 0xff; + dtd->part1.h_high = (((width >> 8) & 0xf) << 4) | ((h_blank_len >> 8) & 0xf); - output_dtd.part1.v_active = height & 0xff; - output_dtd.part1.v_blank = v_blank_len & 0xff; - output_dtd.part1.v_high = (((height >> 8) & 0xf) << 4) | + dtd->part1.v_active = height & 0xff; + dtd->part1.v_blank = v_blank_len & 0xff; + dtd->part1.v_high = (((height >> 8) & 0xf) << 4) | ((v_blank_len >> 8) & 0xf); - output_dtd.part2.h_sync_off = h_sync_offset; - output_dtd.part2.h_sync_width = h_sync_len & 0xff; - output_dtd.part2.v_sync_off_width = (v_sync_offset & 0xf) << 4 | + dtd->part2.h_sync_off = h_sync_offset; + dtd->part2.h_sync_width = h_sync_len & 0xff; + dtd->part2.v_sync_off_width = (v_sync_offset & 0xf) << 4 | (v_sync_len & 0xf); - output_dtd.part2.sync_off_width_high = ((h_sync_offset & 0x300) >> 2) | + dtd->part2.sync_off_width_high = ((h_sync_offset & 0x300) >> 2) | ((h_sync_len & 0x300) >> 4) | ((v_sync_offset & 0x30) >> 2) | ((v_sync_len & 0x30) >> 4); - output_dtd.part2.dtd_flags = 0x18; + dtd->part2.dtd_flags = 0x18; if (mode->flags & DRM_MODE_FLAG_PHSYNC) - output_dtd.part2.dtd_flags |= 0x2; + dtd->part2.dtd_flags |= 0x2; if (mode->flags & DRM_MODE_FLAG_PVSYNC) - output_dtd.part2.dtd_flags |= 0x4; + dtd->part2.dtd_flags |= 0x4; + + dtd->part2.sdvo_flags = 0; + dtd->part2.v_sync_off_high = v_sync_offset & 0xc0; + dtd->part2.reserved = 0; +} + +static void intel_sdvo_get_mode_from_dtd(struct drm_display_mode * mode, + struct intel_sdvo_dtd *dtd) +{ + uint16_t width, height; + uint16_t h_blank_len, h_sync_len, v_blank_len, v_sync_len; + uint16_t h_sync_offset, v_sync_offset; + + width = mode->crtc_hdisplay; + height = mode->crtc_vdisplay; + + /* do some mode translations */ + h_blank_len = mode->crtc_hblank_end - mode->crtc_hblank_start; + h_sync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; + + v_blank_len = mode->crtc_vblank_end - mode->crtc_vblank_start; + v_sync_len = mode->crtc_vsync_end - mode->crtc_vsync_start; + + h_sync_offset = mode->crtc_hsync_start - mode->crtc_hblank_start; + v_sync_offset = mode->crtc_vsync_start - mode->crtc_vblank_start; + + mode->hdisplay = dtd->part1.h_active; + mode->hdisplay += ((dtd->part1.h_high >> 4) & 0x0f) << 8; + mode->hsync_start = mode->hdisplay + dtd->part2.h_sync_off; + mode->hsync_start += (dtd->part2.sync_off_width_high & 0xa0) << 2; + mode->hsync_end = mode->hsync_start + dtd->part2.h_sync_width; + mode->hsync_end += (dtd->part2.sync_off_width_high & 0x30) << 4; + mode->htotal = mode->hdisplay + dtd->part1.h_blank; + mode->htotal += (dtd->part1.h_high & 0xf) << 8; + + mode->vdisplay = dtd->part1.v_active; + mode->vdisplay += ((dtd->part1.v_high >> 4) & 0x0f) << 8; + mode->vsync_start = mode->vdisplay; + mode->vsync_start += (dtd->part2.v_sync_off_width >> 4) & 0xf; + mode->vsync_start += (dtd->part2.sync_off_width_high & 0x0a) << 2; + mode->vsync_start += dtd->part2.v_sync_off_high & 0xc0; + mode->vsync_end = mode->vsync_start + + (dtd->part2.v_sync_off_width & 0xf); + mode->vsync_end += (dtd->part2.sync_off_width_high & 0x3) << 4; + mode->vtotal = mode->vdisplay + dtd->part1.v_blank; + mode->vtotal += (dtd->part1.v_high & 0xf) << 8; + + mode->clock = dtd->part1.clock * 10; + + mode->flags &= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); + if (dtd->part2.dtd_flags & 0x2) + mode->flags |= DRM_MODE_FLAG_PHSYNC; + if (dtd->part2.dtd_flags & 0x4) + mode->flags |= DRM_MODE_FLAG_PVSYNC; +} + +static bool intel_sdvo_get_supp_encode(struct intel_output *output, + struct intel_sdvo_encode *encode) +{ + uint8_t status; + + intel_sdvo_write_cmd(output, SDVO_CMD_GET_SUPP_ENCODE, NULL, 0); + status = intel_sdvo_read_response(output, encode, sizeof(*encode)); + if (status != SDVO_CMD_STATUS_SUCCESS) { /* non-support means DVI */ + memset(encode, 0, sizeof(*encode)); + return false; + } + + return true; +} + +static bool intel_sdvo_set_encode(struct intel_output *output, uint8_t mode) +{ + uint8_t status; + + intel_sdvo_write_cmd(output, SDVO_CMD_SET_ENCODE, &mode, 1); + status = intel_sdvo_read_response(output, NULL, 0); + + return (status == SDVO_CMD_STATUS_SUCCESS); +} + +static bool intel_sdvo_set_colorimetry(struct intel_output *output, + uint8_t mode) +{ + uint8_t status; + + intel_sdvo_write_cmd(output, SDVO_CMD_SET_COLORIMETRY, &mode, 1); + status = intel_sdvo_read_response(output, NULL, 0); + + return (status == SDVO_CMD_STATUS_SUCCESS); +} + +#if 0 +static void intel_sdvo_dump_hdmi_buf(struct intel_output *output) +{ + int i, j; + uint8_t set_buf_index[2]; + uint8_t av_split; + uint8_t buf_size; + uint8_t buf[48]; + uint8_t *pos; + + intel_sdvo_write_cmd(output, SDVO_CMD_GET_HBUF_AV_SPLIT, NULL, 0); + intel_sdvo_read_response(output, &av_split, 1); + + for (i = 0; i <= av_split; i++) { + set_buf_index[0] = i; set_buf_index[1] = 0; + intel_sdvo_write_cmd(output, SDVO_CMD_SET_HBUF_INDEX, + set_buf_index, 2); + intel_sdvo_write_cmd(output, SDVO_CMD_GET_HBUF_INFO, NULL, 0); + intel_sdvo_read_response(output, &buf_size, 1); + + pos = buf; + for (j = 0; j <= buf_size; j += 8) { + intel_sdvo_write_cmd(output, SDVO_CMD_GET_HBUF_DATA, + NULL, 0); + intel_sdvo_read_response(output, pos, 8); + pos += 8; + } + } +} +#endif + +static void intel_sdvo_set_hdmi_buf(struct intel_output *output, int index, + uint8_t *data, int8_t size, uint8_t tx_rate) +{ + uint8_t set_buf_index[2]; + + set_buf_index[0] = index; + set_buf_index[1] = 0; + + intel_sdvo_write_cmd(output, SDVO_CMD_SET_HBUF_INDEX, set_buf_index, 2); + + for (; size > 0; size -= 8) { + intel_sdvo_write_cmd(output, SDVO_CMD_SET_HBUF_DATA, data, 8); + data += 8; + } + + intel_sdvo_write_cmd(output, SDVO_CMD_SET_HBUF_TXRATE, &tx_rate, 1); +} + +static uint8_t intel_sdvo_calc_hbuf_csum(uint8_t *data, uint8_t size) +{ + uint8_t csum = 0; + int i; + + for (i = 0; i < size; i++) + csum += data[i]; + + return 0x100 - csum; +} + +#define DIP_TYPE_AVI 0x82 +#define DIP_VERSION_AVI 0x2 +#define DIP_LEN_AVI 13 + +struct dip_infoframe { + uint8_t type; + uint8_t version; + uint8_t len; + uint8_t checksum; + union { + struct { + /* Packet Byte #1 */ + uint8_t S:2; + uint8_t B:2; + uint8_t A:1; + uint8_t Y:2; + uint8_t rsvd1:1; + /* Packet Byte #2 */ + uint8_t R:4; + uint8_t M:2; + uint8_t C:2; + /* Packet Byte #3 */ + uint8_t SC:2; + uint8_t Q:2; + uint8_t EC:3; + uint8_t ITC:1; + /* Packet Byte #4 */ + uint8_t VIC:7; + uint8_t rsvd2:1; + /* Packet Byte #5 */ + uint8_t PR:4; + uint8_t rsvd3:4; + /* Packet Byte #6~13 */ + uint16_t top_bar_end; + uint16_t bottom_bar_start; + uint16_t left_bar_end; + uint16_t right_bar_start; + } avi; + struct { + /* Packet Byte #1 */ + uint8_t channel_count:3; + uint8_t rsvd1:1; + uint8_t coding_type:4; + /* Packet Byte #2 */ + uint8_t sample_size:2; /* SS0, SS1 */ + uint8_t sample_frequency:3; + uint8_t rsvd2:3; + /* Packet Byte #3 */ + uint8_t coding_type_private:5; + uint8_t rsvd3:3; + /* Packet Byte #4 */ + uint8_t channel_allocation; + /* Packet Byte #5 */ + uint8_t rsvd4:3; + uint8_t level_shift:4; + uint8_t downmix_inhibit:1; + } audio; + uint8_t payload[28]; + } __attribute__ ((packed)) u; +} __attribute__((packed)); + +static void intel_sdvo_set_avi_infoframe(struct intel_output *output, + struct drm_display_mode * mode) +{ + struct dip_infoframe avi_if = { + .type = DIP_TYPE_AVI, + .version = DIP_VERSION_AVI, + .len = DIP_LEN_AVI, + }; + + avi_if.checksum = intel_sdvo_calc_hbuf_csum((uint8_t *)&avi_if, + 4 + avi_if.len); + intel_sdvo_set_hdmi_buf(output, 1, (uint8_t *)&avi_if, 4 + avi_if.len, + SDVO_HBUF_TX_VSYNC); +} + +static bool intel_sdvo_mode_fixup(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct intel_output *output = enc_to_intel_output(encoder); + struct intel_sdvo_priv *dev_priv = output->dev_priv; - output_dtd.part2.sdvo_flags = 0; - output_dtd.part2.v_sync_off_high = v_sync_offset & 0xc0; - output_dtd.part2.reserved = 0; + if (!dev_priv->is_tv) { + /* Make the CRTC code factor in the SDVO pixel multiplier. The + * SDVO device will be told of the multiplier during mode_set. + */ + adjusted_mode->clock *= intel_sdvo_get_pixel_multiplier(mode); + } else { + struct intel_sdvo_dtd output_dtd; + bool success; + + /* We need to construct preferred input timings based on our + * output timings. To do that, we have to set the output + * timings, even though this isn't really the right place in + * the sequence to do it. Oh well. + */ + + + /* Set output timings */ + intel_sdvo_get_dtd_from_mode(&output_dtd, mode); + intel_sdvo_set_target_output(output, + dev_priv->controlled_output); + intel_sdvo_set_output_timing(output, &output_dtd); + + /* Set the input timing to the screen. Assume always input 0. */ + intel_sdvo_set_target_input(output, true, false); + + + success = intel_sdvo_create_preferred_input_timing(output, + mode->clock / 10, + mode->hdisplay, + mode->vdisplay); + if (success) { + struct intel_sdvo_dtd input_dtd; - /* Set the output timing to the screen */ - intel_sdvo_set_target_output(intel_output, sdvo_priv->active_outputs); - intel_sdvo_set_output_timing(intel_output, &output_dtd); + intel_sdvo_get_preferred_input_timing(output, + &input_dtd); + intel_sdvo_get_mode_from_dtd(adjusted_mode, &input_dtd); + + } else { + return false; + } + } + return true; +} + +static void intel_sdvo_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 drm_crtc *crtc = encoder->crtc; + struct intel_crtc *intel_crtc = to_intel_crtc(crtc); + struct intel_output *output = enc_to_intel_output(encoder); + struct intel_sdvo_priv *sdvo_priv = output->dev_priv; + u32 sdvox = 0; + int sdvo_pixel_multiply; + struct intel_sdvo_in_out_map in_out; + struct intel_sdvo_dtd input_dtd; + u8 status; + + if (!mode) + return; + + /* First, set the input mapping for the first input to our controlled + * output. This is only correct if we're a single-input device, in + * which case the first input is the output from the appropriate SDVO + * channel on the motherboard. In a two-input device, the first input + * will be SDVOB and the second SDVOC. + */ + in_out.in0 = sdvo_priv->controlled_output; + in_out.in1 = 0; + + intel_sdvo_write_cmd(output, SDVO_CMD_SET_IN_OUT_MAP, + &in_out, sizeof(in_out)); + status = intel_sdvo_read_response(output, NULL, 0); + + if (sdvo_priv->is_hdmi) { + intel_sdvo_set_avi_infoframe(output, mode); + sdvox |= SDVO_AUDIO_ENABLE; + } + + intel_sdvo_get_dtd_from_mode(&input_dtd, mode); + + /* If it's a TV, we already set the output timing in mode_fixup. + * Otherwise, the output timing is equal to the input timing. + */ + if (!sdvo_priv->is_tv) { + /* Set the output timing to the screen */ + intel_sdvo_set_target_output(output, + sdvo_priv->controlled_output); + intel_sdvo_set_output_timing(output, &input_dtd); + } /* Set the input timing to the screen. Assume always input 0. */ - intel_sdvo_set_target_input(intel_output, true, false); + intel_sdvo_set_target_input(output, true, false); - /* We would like to use i830_sdvo_create_preferred_input_timing() to + /* We would like to use intel_sdvo_create_preferred_input_timing() to * provide the device with a timing it can support, if it supports that * feature. However, presumably we would need to adjust the CRTC to * output the preferred timing, and we don't support that currently. */ - intel_sdvo_set_input_timing(intel_output, &output_dtd); +#if 0 + success = intel_sdvo_create_preferred_input_timing(output, clock, + width, height); + if (success) { + struct intel_sdvo_dtd *input_dtd; + + intel_sdvo_get_preferred_input_timing(output, &input_dtd); + intel_sdvo_set_input_timing(output, &input_dtd); + } +#else + intel_sdvo_set_input_timing(output, &input_dtd); +#endif switch (intel_sdvo_get_pixel_multiplier(mode)) { case 1: - intel_sdvo_set_clock_rate_mult(intel_output, + intel_sdvo_set_clock_rate_mult(output, SDVO_CLOCK_RATE_MULT_1X); break; case 2: - intel_sdvo_set_clock_rate_mult(intel_output, + intel_sdvo_set_clock_rate_mult(output, SDVO_CLOCK_RATE_MULT_2X); break; case 4: - intel_sdvo_set_clock_rate_mult(intel_output, + intel_sdvo_set_clock_rate_mult(output, SDVO_CLOCK_RATE_MULT_4X); break; } /* Set the SDVO control regs. */ - if (0/*IS_I965GM(dev)*/) { - sdvox = SDVO_BORDER_ENABLE; - } else { - sdvox = I915_READ(sdvo_priv->output_device); - switch (sdvo_priv->output_device) { - case SDVOB: - sdvox &= SDVOB_PRESERVE_MASK; - break; - case SDVOC: - sdvox &= SDVOC_PRESERVE_MASK; - break; - } - sdvox |= (9 << 19) | SDVO_BORDER_ENABLE; - } + if (IS_I965G(dev)) { + sdvox |= SDVO_BORDER_ENABLE | + SDVO_VSYNC_ACTIVE_HIGH | + SDVO_HSYNC_ACTIVE_HIGH; + } else { + sdvox |= I915_READ(sdvo_priv->output_device); + switch (sdvo_priv->output_device) { + case SDVOB: + sdvox &= SDVOB_PRESERVE_MASK; + break; + case SDVOC: + sdvox &= SDVOC_PRESERVE_MASK; + break; + } + sdvox |= (9 << 19) | SDVO_BORDER_ENABLE; + } if (intel_crtc->pipe == 1) sdvox |= SDVO_PIPE_B_SELECT; sdvo_pixel_multiply = intel_sdvo_get_pixel_multiplier(mode); if (IS_I965G(dev)) { - /* done in crtc_mode_set as the dpll_md reg must be written - early */ - } else if (IS_I945G(dev) || IS_I945GM(dev)) { - /* done in crtc_mode_set as it lives inside the - dpll register */ + /* done in crtc_mode_set as the dpll_md reg must be written early */ + } else if (IS_I945G(dev) || IS_I945GM(dev) || IS_G33(dev)) { + /* done in crtc_mode_set as it lives inside the dpll register */ } else { sdvox |= (sdvo_pixel_multiply - 1) << SDVO_PORT_MULTIPLY_SHIFT; } - intel_sdvo_write_sdvox(intel_output, sdvox); + intel_sdvo_write_sdvox(output, sdvox); } static void intel_sdvo_dpms(struct drm_encoder *encoder, int mode) @@ -714,7 +1137,7 @@ static void intel_sdvo_dpms(struct drm_encoder *encoder, int mode) if (0) intel_sdvo_set_encoder_power_state(intel_output, mode); - intel_sdvo_set_active_outputs(intel_output, sdvo_priv->active_outputs); + intel_sdvo_set_active_outputs(intel_output, sdvo_priv->controlled_output); } return; } @@ -752,6 +1175,9 @@ static void intel_sdvo_save(struct drm_connector *connector) &sdvo_priv->save_output_dtd[o]); } } + if (sdvo_priv->is_tv) { + /* XXX: Save TV format/enhancements. */ + } sdvo_priv->save_SDVOX = I915_READ(sdvo_priv->output_device); } @@ -759,7 +1185,6 @@ static void intel_sdvo_save(struct drm_connector *connector) static void intel_sdvo_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_sdvo_priv *sdvo_priv = intel_output->dev_priv; int o; @@ -790,7 +1215,11 @@ static void intel_sdvo_restore(struct drm_connector *connector) intel_sdvo_set_clock_rate_mult(intel_output, sdvo_priv->save_sdvo_mult); - I915_WRITE(sdvo_priv->output_device, sdvo_priv->save_SDVOX); + if (sdvo_priv->is_tv) { + /* XXX: Restore TV format/enhancements. */ + } + + intel_sdvo_write_sdvox(intel_output, sdvo_priv->save_SDVOX); if (sdvo_priv->save_SDVOX & SDVO_ENABLE) { @@ -916,20 +1345,173 @@ static enum drm_connector_status intel_sdvo_detect(struct drm_connector *connect status = intel_sdvo_read_response(intel_output, &response, 2); DRM_DEBUG("SDVO response %d %d\n", response[0], response[1]); + + if (status != SDVO_CMD_STATUS_SUCCESS) + return connector_status_unknown; + if ((response[0] != 0) || (response[1] != 0)) return connector_status_connected; else return connector_status_disconnected; } -static int intel_sdvo_get_modes(struct drm_connector *connector) +static void intel_sdvo_get_ddc_modes(struct drm_connector *connector) { struct intel_output *intel_output = to_intel_output(connector); + struct intel_sdvo_priv *sdvo_priv = intel_output->dev_priv; /* set the bus switch and get the modes */ - intel_sdvo_set_control_bus_switch(intel_output, SDVO_CONTROL_BUS_DDC2); + intel_sdvo_set_control_bus_switch(intel_output, sdvo_priv->ddc_bus); intel_ddc_get_modes(intel_output); +#if 0 + struct drm_device *dev = encoder->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + /* Mac mini hack. On this device, I get DDC through the analog, which + * load-detects as disconnected. I fail to DDC through the SDVO DDC, + * but it does load-detect as connected. So, just steal the DDC bits + * from analog when we fail at finding it the right way. + */ + crt = xf86_config->output[0]; + intel_output = crt->driver_private; + if (intel_output->type == I830_OUTPUT_ANALOG && + crt->funcs->detect(crt) == XF86OutputStatusDisconnected) { + I830I2CInit(pScrn, &intel_output->pDDCBus, GPIOA, "CRTDDC_A"); + edid_mon = xf86OutputGetEDID(crt, intel_output->pDDCBus); + xf86DestroyI2CBusRec(intel_output->pDDCBus, true, true); + } + if (edid_mon) { + xf86OutputSetEDID(output, edid_mon); + modes = xf86OutputGetEDIDModes(output); + } +#endif +} + +/** + * This function checks the current TV format, and chooses a default if + * it hasn't been set. + */ +static void +intel_sdvo_check_tv_format(struct intel_output *output) +{ + struct intel_sdvo_priv *dev_priv = output->dev_priv; + struct intel_sdvo_tv_format format, unset; + uint8_t status; + + intel_sdvo_write_cmd(output, SDVO_CMD_GET_TV_FORMAT, NULL, 0); + status = intel_sdvo_read_response(output, &format, sizeof(format)); + if (status != SDVO_CMD_STATUS_SUCCESS) + return; + + memset(&unset, 0, sizeof(unset)); + if (memcmp(&format, &unset, sizeof(format))) { + DRM_DEBUG("%s: Choosing default TV format of NTSC-M\n", + SDVO_NAME(dev_priv)); + + format.ntsc_m = true; + intel_sdvo_write_cmd(output, SDVO_CMD_SET_TV_FORMAT, NULL, 0); + status = intel_sdvo_read_response(output, NULL, 0); + } +} + +/* + * Set of SDVO TV modes. + * Note! This is in reply order (see loop in get_tv_modes). + * XXX: all 60Hz refresh? + */ +struct drm_display_mode sdvo_tv_modes[] = { + { DRM_MODE("320x200", DRM_MODE_TYPE_DRIVER, 5815680, 321, 384, 416, + 200, 0, 232, 201, 233, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("320x240", DRM_MODE_TYPE_DRIVER, 6814080, 321, 384, 416, + 240, 0, 272, 241, 273, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("400x300", DRM_MODE_TYPE_DRIVER, 9910080, 401, 464, 496, + 300, 0, 332, 301, 333, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("640x350", DRM_MODE_TYPE_DRIVER, 16913280, 641, 704, 736, + 350, 0, 382, 351, 383, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("640x400", DRM_MODE_TYPE_DRIVER, 19121280, 641, 704, 736, + 400, 0, 432, 401, 433, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("640x400", DRM_MODE_TYPE_DRIVER, 19121280, 641, 704, 736, + 400, 0, 432, 401, 433, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("704x480", DRM_MODE_TYPE_DRIVER, 24624000, 705, 768, 800, + 480, 0, 512, 481, 513, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("704x576", DRM_MODE_TYPE_DRIVER, 29232000, 705, 768, 800, + 576, 0, 608, 577, 609, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("720x350", DRM_MODE_TYPE_DRIVER, 18751680, 721, 784, 816, + 350, 0, 382, 351, 383, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 21199680, 721, 784, 816, + 400, 0, 432, 401, 433, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 25116480, 721, 784, 816, + 480, 0, 512, 481, 513, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("720x540", DRM_MODE_TYPE_DRIVER, 28054080, 721, 784, 816, + 540, 0, 572, 541, 573, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 29816640, 721, 784, 816, + 576, 0, 608, 577, 609, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("768x576", DRM_MODE_TYPE_DRIVER, 31570560, 769, 832, 864, + 576, 0, 608, 577, 609, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 34030080, 801, 864, 896, + 600, 0, 632, 601, 633, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("832x624", DRM_MODE_TYPE_DRIVER, 36581760, 833, 896, 928, + 624, 0, 656, 625, 657, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("920x766", DRM_MODE_TYPE_DRIVER, 48707040, 921, 984, 1016, + 766, 0, 798, 767, 799, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 53827200, 1025, 1088, 1120, + 768, 0, 800, 769, 801, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 87265920, 1281, 1344, 1376, + 1024, 0, 1056, 1025, 1057, 4196112, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +}; + +static void intel_sdvo_get_tv_modes(struct drm_connector *connector) +{ + struct intel_output *output = to_intel_output(connector); + uint32_t reply = 0; + uint8_t status; + int i = 0; + + intel_sdvo_check_tv_format(output); + + /* Read the list of supported input resolutions for the selected TV + * format. + */ + intel_sdvo_write_cmd(output, SDVO_CMD_GET_SDTV_RESOLUTION_SUPPORT, + NULL, 0); + status = intel_sdvo_read_response(output, &reply, 3); + if (status != SDVO_CMD_STATUS_SUCCESS) + return; + + for (i = 0; i < ARRAY_SIZE(sdvo_tv_modes); i++) + if (reply & (1 << i)) + drm_mode_probed_add(connector, &sdvo_tv_modes[i]); +} + +static int intel_sdvo_get_modes(struct drm_connector *connector) +{ + struct intel_output *output = to_intel_output(connector); + struct intel_sdvo_priv *sdvo_priv = output->dev_priv; + + if (sdvo_priv->is_tv) + intel_sdvo_get_tv_modes(connector); + else + intel_sdvo_get_ddc_modes(connector); + if (list_empty(&connector->probed_modes)) return 0; return 1; @@ -978,6 +1560,65 @@ static const struct drm_encoder_funcs intel_sdvo_enc_funcs = { }; +/** + * Choose the appropriate DDC bus for control bus switch command for this + * SDVO output based on the controlled output. + * + * DDC bus number assignment is in a priority order of RGB outputs, then TMDS + * outputs, then LVDS outputs. + */ +static void +intel_sdvo_select_ddc_bus(struct intel_sdvo_priv *dev_priv) +{ + uint16_t mask = 0; + unsigned int num_bits; + + /* Make a mask of outputs less than or equal to our own priority in the + * list. + */ + switch (dev_priv->controlled_output) { + case SDVO_OUTPUT_LVDS1: + mask |= SDVO_OUTPUT_LVDS1; + case SDVO_OUTPUT_LVDS0: + mask |= SDVO_OUTPUT_LVDS0; + case SDVO_OUTPUT_TMDS1: + mask |= SDVO_OUTPUT_TMDS1; + case SDVO_OUTPUT_TMDS0: + mask |= SDVO_OUTPUT_TMDS0; + case SDVO_OUTPUT_RGB1: + mask |= SDVO_OUTPUT_RGB1; + case SDVO_OUTPUT_RGB0: + mask |= SDVO_OUTPUT_RGB0; + break; + } + + /* Count bits to find what number we are in the priority list. */ + mask &= dev_priv->caps.output_flags; + num_bits = hweight16(mask); + if (num_bits > 3) { + /* if more than 3 outputs, default to DDC bus 3 for now */ + num_bits = 3; + } + + /* Corresponds to SDVO_CONTROL_BUS_DDCx */ + dev_priv->ddc_bus = 1 << num_bits; +} + +static bool +intel_sdvo_get_digital_encoding_mode(struct intel_output *output) +{ + struct intel_sdvo_priv *sdvo_priv = output->dev_priv; + uint8_t status; + + intel_sdvo_set_target_output(output, sdvo_priv->controlled_output); + + intel_sdvo_write_cmd(output, SDVO_CMD_GET_ENCODE, NULL, 0); + status = intel_sdvo_read_response(output, &sdvo_priv->is_hdmi, 1); + if (status != SDVO_CMD_STATUS_SUCCESS) + return false; + return true; +} + bool intel_sdvo_init(struct drm_device *dev, int output_device) { struct drm_connector *connector; @@ -1040,45 +1681,76 @@ bool intel_sdvo_init(struct drm_device *dev, int output_device) intel_sdvo_get_capabilities(intel_output, &sdvo_priv->caps); - memset(&sdvo_priv->active_outputs, 0, sizeof(sdvo_priv->active_outputs)); + if (sdvo_priv->caps.output_flags & + (SDVO_OUTPUT_TMDS0 | SDVO_OUTPUT_TMDS1)) { + if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_TMDS0) + sdvo_priv->controlled_output = SDVO_OUTPUT_TMDS0; + else + sdvo_priv->controlled_output = SDVO_OUTPUT_TMDS1; + + connector->display_info.subpixel_order = SubPixelHorizontalRGB; + encoder_type = DRM_MODE_ENCODER_TMDS; + connector_type = DRM_MODE_CONNECTOR_DVID; - /* TODO, CVBS, SVID, YPRPB & SCART outputs. */ - if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_RGB0) + if (intel_sdvo_get_supp_encode(intel_output, + &sdvo_priv->encode) && + intel_sdvo_get_digital_encoding_mode(intel_output) && + sdvo_priv->is_hdmi) { + /* enable hdmi encoding mode if supported */ + intel_sdvo_set_encode(intel_output, SDVO_ENCODE_HDMI); + intel_sdvo_set_colorimetry(intel_output, + SDVO_COLORIMETRY_RGB256); + connector_type = DRM_MODE_CONNECTOR_HDMIA; + } + } + else if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_SVID0) { - sdvo_priv->active_outputs = SDVO_OUTPUT_RGB0; + sdvo_priv->controlled_output = SDVO_OUTPUT_SVID0; + connector->display_info.subpixel_order = SubPixelHorizontalRGB; + encoder_type = DRM_MODE_ENCODER_TVDAC; + connector_type = DRM_MODE_CONNECTOR_SVIDEO; + sdvo_priv->is_tv = true; + intel_output->needs_tv_clock = true; + } + else if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_RGB0) + { + sdvo_priv->controlled_output = SDVO_OUTPUT_RGB0; connector->display_info.subpixel_order = SubPixelHorizontalRGB; encoder_type = DRM_MODE_ENCODER_DAC; connector_type = DRM_MODE_CONNECTOR_VGA; } else if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_RGB1) { - sdvo_priv->active_outputs = SDVO_OUTPUT_RGB1; + sdvo_priv->controlled_output = SDVO_OUTPUT_RGB1; connector->display_info.subpixel_order = SubPixelHorizontalRGB; encoder_type = DRM_MODE_ENCODER_DAC; connector_type = DRM_MODE_CONNECTOR_VGA; } - else if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_TMDS0) + else if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_LVDS0) { - sdvo_priv->active_outputs = SDVO_OUTPUT_TMDS0; + sdvo_priv->controlled_output = SDVO_OUTPUT_LVDS0; connector->display_info.subpixel_order = SubPixelHorizontalRGB; - encoder_type = DRM_MODE_ENCODER_TMDS; - connector_type = DRM_MODE_CONNECTOR_DVID; + encoder_type = DRM_MODE_ENCODER_LVDS; + connector_type = DRM_MODE_CONNECTOR_LVDS; } - else if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_TMDS1) + else if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_LVDS1) { - sdvo_priv->active_outputs = SDVO_OUTPUT_TMDS1; + sdvo_priv->controlled_output = SDVO_OUTPUT_LVDS1; connector->display_info.subpixel_order = SubPixelHorizontalRGB; - encoder_type = DRM_MODE_ENCODER_TMDS; - connector_type = DRM_MODE_CONNECTOR_DVID; + encoder_type = DRM_MODE_ENCODER_LVDS; + connector_type = DRM_MODE_CONNECTOR_LVDS; } else { unsigned char bytes[2]; + sdvo_priv->controlled_output = 0; memcpy (bytes, &sdvo_priv->caps.output_flags, 2); - DRM_DEBUG("%s: No active RGB or TMDS outputs (0x%02x%02x)\n", + DRM_DEBUG("%s: Unknown SDVO output type (0x%02x%02x)\n", SDVO_NAME(sdvo_priv), bytes[0], bytes[1]); + encoder_type = DRM_MODE_ENCODER_NONE; + connector_type = DRM_MODE_CONNECTOR_Unknown; goto err_i2c; } @@ -1089,6 +1761,8 @@ bool intel_sdvo_init(struct drm_device *dev, int output_device) drm_mode_connector_attach_encoder(&intel_output->base, &intel_output->enc); drm_sysfs_connector_add(connector); + intel_sdvo_select_ddc_bus(sdvo_priv); + /* Set the input timing to the screen. Assume always input 0. */ intel_sdvo_set_target_input(intel_output, true, false); |