diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/gpu/drm/drm_edid.c | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2 |
samsung update 1
Diffstat (limited to 'drivers/gpu/drm/drm_edid.c')
-rw-r--r-- | drivers/gpu/drm/drm_edid.c | 261 |
1 files changed, 238 insertions, 23 deletions
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 1bbb85b..b807cbe 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -148,8 +148,7 @@ EXPORT_SYMBOL(drm_edid_header_is_valid); * Sanity check the EDID block (base or extension). Return 0 if the block * doesn't check out, or 1 if it's valid. */ -static bool -drm_edid_block_valid(u8 *raw_edid) +bool drm_edid_block_valid(u8 *raw_edid) { int i; u8 csum = 0; @@ -197,11 +196,12 @@ drm_edid_block_valid(u8 *raw_edid) bad: if (raw_edid) { printk(KERN_ERR "Raw EDID:\n"); - print_hex_dump_bytes(KERN_ERR, DUMP_PREFIX_NONE, raw_edid, EDID_LENGTH); - printk(KERN_ERR "\n"); + print_hex_dump(KERN_ERR, " \t", DUMP_PREFIX_NONE, 16, 1, + raw_edid, EDID_LENGTH, false); } return 0; } +EXPORT_SYMBOL(drm_edid_block_valid); /** * drm_edid_is_valid - sanity check EDID data @@ -225,7 +225,6 @@ bool drm_edid_is_valid(struct edid *edid) } EXPORT_SYMBOL(drm_edid_is_valid); -#define DDC_ADDR 0x50 #define DDC_SEGMENT_ADDR 0x30 /** * Get EDID information via I2C. @@ -265,6 +264,11 @@ drm_do_probe_ddc_edid(struct i2c_adapter *adapter, unsigned char *buf, } }; ret = i2c_transfer(adapter, msgs, 2); + if (ret == -ENXIO) { + DRM_DEBUG_KMS("drm: skipping non-existent adapter %s\n", + adapter->name); + break; + } } while (ret != 2 && --retries); return ret == 2 ? 0 : -1; @@ -507,25 +511,10 @@ static void cea_for_each_detailed_block(u8 *ext, detailed_cb *cb, void *closure) { int i, n = 0; - u8 rev = ext[0x01], d = ext[0x02]; + u8 d = ext[0x02]; u8 *det_base = ext + d; - switch (rev) { - case 0: - /* can't happen */ - return; - case 1: - /* have to infer how many blocks we have, check pixel clock */ - for (i = 0; i < 6; i++) - if (det_base[18*i] || det_base[18*i+1]) - n++; - break; - default: - /* explicit count */ - n = min(ext[0x03] & 0x0f, 6); - break; - } - + n = (127 - d) / 18; for (i = 0; i < n; i++) cb((struct detailed_timing *)(det_base + 18 * i), closure); } @@ -759,7 +748,7 @@ drm_mode_std(struct drm_connector *connector, struct edid *edid, */ mode = drm_gtf_mode(dev, hsize, vsize, vrefresh_rate, 0, 0); if (drm_mode_hsync(mode) > drm_gtf2_hbreak(edid)) { - kfree(mode); + drm_mode_destroy(dev, mode); mode = drm_gtf_mode_complex(dev, hsize, vsize, vrefresh_rate, 0, 0, drm_gtf2_m(edid), @@ -1318,7 +1307,9 @@ add_detailed_modes(struct drm_connector *connector, struct edid *edid, #define HDMI_IDENTIFIER 0x000C03 #define AUDIO_BLOCK 0x01 +#define VIDEO_BLOCK 0x02 #define VENDOR_BLOCK 0x03 +#define SPEAKER_BLOCK 0x04 #define EDID_BASIC_AUDIO (1 << 6) /** @@ -1347,6 +1338,220 @@ u8 *drm_find_cea_extension(struct edid *edid) } EXPORT_SYMBOL(drm_find_cea_extension); +static int +do_cea_modes (struct drm_connector *connector, u8 *db, u8 len) +{ + struct drm_device *dev = connector->dev; + u8 * mode, cea_mode; + int modes = 0; + + for (mode = db; mode < db + len; mode++) { + cea_mode = (*mode & 127) - 1; /* CEA modes are numbered 1..127 */ + if (cea_mode < drm_num_cea_modes) { + struct drm_display_mode *newmode; + newmode = drm_mode_duplicate(dev, + &edid_cea_modes[cea_mode]); + if (newmode) { + drm_mode_probed_add(connector, newmode); + modes++; + } + } + } + + return modes; +} + +static int +add_cea_modes(struct drm_connector *connector, struct edid *edid) +{ + u8 * cea = drm_find_cea_extension(edid); + u8 * db, dbl; + int modes = 0; + + if (cea && cea[1] >= 3) { + for (db = cea + 4; db < cea + cea[2]; db += dbl + 1) { + dbl = db[0] & 0x1f; + if (((db[0] & 0xe0) >> 5) == VIDEO_BLOCK) + modes += do_cea_modes (connector, db+1, dbl); + } + } + + return modes; +} + +static void +parse_hdmi_vsdb(struct drm_connector *connector, uint8_t *db) +{ + connector->eld[5] |= (db[6] >> 7) << 1; /* Supports_AI */ + + connector->dvi_dual = db[6] & 1; + connector->max_tmds_clock = db[7] * 5; + + connector->latency_present[0] = db[8] >> 7; + connector->latency_present[1] = (db[8] >> 6) & 1; + connector->video_latency[0] = db[9]; + connector->audio_latency[0] = db[10]; + connector->video_latency[1] = db[11]; + connector->audio_latency[1] = db[12]; + + DRM_LOG_KMS("HDMI: DVI dual %d, " + "max TMDS clock %d, " + "latency present %d %d, " + "video latency %d %d, " + "audio latency %d %d\n", + connector->dvi_dual, + connector->max_tmds_clock, + (int) connector->latency_present[0], + (int) connector->latency_present[1], + connector->video_latency[0], + connector->video_latency[1], + connector->audio_latency[0], + connector->audio_latency[1]); +} + +static void +monitor_name(struct detailed_timing *t, void *data) +{ + if (t->data.other_data.type == EDID_DETAIL_MONITOR_NAME) + *(u8 **)data = t->data.other_data.data.str.str; +} + +/** + * drm_edid_to_eld - build ELD from EDID + * @connector: connector corresponding to the HDMI/DP sink + * @edid: EDID to parse + * + * Fill the ELD (EDID-Like Data) buffer for passing to the audio driver. + * Some ELD fields are left to the graphics driver caller: + * - Conn_Type + * - HDCP + * - Port_ID + */ +void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) +{ + uint8_t *eld = connector->eld; + u8 *cea; + u8 *name; + u8 *db; + int sad_count = 0; + int mnl; + int dbl; + + memset(eld, 0, sizeof(connector->eld)); + + cea = drm_find_cea_extension(edid); + if (!cea) { + DRM_DEBUG_KMS("ELD: no CEA Extension found\n"); + return; + } + + name = NULL; + drm_for_each_detailed_block((u8 *)edid, monitor_name, &name); + for (mnl = 0; name && mnl < 13; mnl++) { + if (name[mnl] == 0x0a) + break; + eld[20 + mnl] = name[mnl]; + } + eld[4] = (cea[1] << 5) | mnl; + DRM_DEBUG_KMS("ELD monitor %s\n", eld + 20); + + eld[0] = 2 << 3; /* ELD version: 2 */ + + eld[16] = edid->mfg_id[0]; + eld[17] = edid->mfg_id[1]; + eld[18] = edid->prod_code[0]; + eld[19] = edid->prod_code[1]; + + if (cea[1] >= 3) + for (db = cea + 4; db < cea + cea[2]; db += dbl + 1) { + dbl = db[0] & 0x1f; + + switch ((db[0] & 0xe0) >> 5) { + case AUDIO_BLOCK: + /* Audio Data Block, contains SADs */ + sad_count = dbl / 3; + memcpy(eld + 20 + mnl, &db[1], dbl); + break; + case SPEAKER_BLOCK: + /* Speaker Allocation Data Block */ + eld[7] = db[1]; + break; + case VENDOR_BLOCK: + /* HDMI Vendor-Specific Data Block */ + if (db[1] == 0x03 && db[2] == 0x0c && db[3] == 0) + parse_hdmi_vsdb(connector, db); + break; + default: + break; + } + } + eld[5] |= sad_count << 4; + eld[2] = (20 + mnl + sad_count * 3 + 3) / 4; + + DRM_DEBUG_KMS("ELD size %d, SAD count %d\n", (int)eld[2], sad_count); +} +EXPORT_SYMBOL(drm_edid_to_eld); + +/** + * drm_av_sync_delay - HDMI/DP sink audio-video sync delay in millisecond + * @connector: connector associated with the HDMI/DP sink + * @mode: the display mode + */ +int drm_av_sync_delay(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + int i = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); + int a, v; + + if (!connector->latency_present[0]) + return 0; + if (!connector->latency_present[1]) + i = 0; + + a = connector->audio_latency[i]; + v = connector->video_latency[i]; + + /* + * HDMI/DP sink doesn't support audio or video? + */ + if (a == 255 || v == 255) + return 0; + + /* + * Convert raw EDID values to millisecond. + * Treat unknown latency as 0ms. + */ + if (a) + a = min(2 * (a - 1), 500); + if (v) + v = min(2 * (v - 1), 500); + + return max(v - a, 0); +} +EXPORT_SYMBOL(drm_av_sync_delay); + +/** + * drm_select_eld - select one ELD from multiple HDMI/DP sinks + * @encoder: the encoder just changed display mode + * @mode: the adjusted display mode + * + * It's possible for one encoder to be associated with multiple HDMI/DP sinks. + * The policy is now hard coded to simply use the first HDMI/DP sink's ELD. + */ +struct drm_connector *drm_select_eld(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + struct drm_connector *connector; + struct drm_device *dev = encoder->dev; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) + if (connector->encoder == encoder && connector->eld[0]) + return connector; + + return NULL; +} +EXPORT_SYMBOL(drm_select_eld); + /** * drm_detect_hdmi_monitor - detect whether monitor is hdmi. * @edid: monitor EDID information @@ -1451,6 +1656,8 @@ EXPORT_SYMBOL(drm_detect_monitor_audio); static void drm_add_display_info(struct edid *edid, struct drm_display_info *info) { + u8 *edid_ext; + info->width_mm = edid->width_cm * 10; info->height_mm = edid->height_cm * 10; @@ -1495,6 +1702,13 @@ static void drm_add_display_info(struct edid *edid, info->color_formats = DRM_COLOR_FORMAT_YCRCB444; if (info->color_formats & DRM_EDID_FEATURE_RGB_YCRCB422) info->color_formats = DRM_COLOR_FORMAT_YCRCB422; + + /* Get data from CEA blocks if present */ + edid_ext = drm_find_cea_extension(edid); + if (!edid_ext) + return; + + info->cea_rev = edid_ext[1]; } /** @@ -1541,6 +1755,7 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid) num_modes += add_standard_modes(connector, edid); num_modes += add_established_modes(connector, edid); num_modes += add_inferred_modes(connector, edid); + num_modes += add_cea_modes(connector, edid); if (quirks & (EDID_QUIRK_PREFER_LARGE_60 | EDID_QUIRK_PREFER_LARGE_75)) edid_fixup_preferred(connector, quirks); |