/* * Copyright © 2019 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: Simon Ser */ #include "config.h" #include #include #include #include #include #include "igt_aux.h" #include "igt_core.h" #include "igt_edid.h" /** * SECTION:igt_edid * @short_description: EDID generation library * @title: EDID * @include: igt_edid.h * * This library contains helpers to generate custom EDIDs. * The E-EDID specification is available at: * https://glenwing.github.io/docs/VESA-EEDID-A2.pdf * * The EDID CEA extension is defined in CEA-861-D section 7. The HDMI VSDB is * defined in the HDMI spec. */ static const char edid_header[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; static const char monitor_range_padding[] = { 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }; const uint8_t hdmi_ieee_oui[3] = {0x03, 0x0C, 0x00}; /* vfreq is in Hz */ static void std_timing_set(struct std_timing *st, int hsize, int vfreq, enum std_timing_aspect aspect) { assert(hsize >= 256 && hsize <= 2288); st->hsize = hsize / 8 - 31; st->vfreq_aspect = aspect << 6 | (vfreq - 60); } static void std_timing_unset(struct std_timing *st) { memset(st, 0x01, sizeof(struct std_timing)); } /** * detailed_timing_set_mode: fill a detailed timing based on a mode */ void detailed_timing_set_mode(struct detailed_timing *dt, drmModeModeInfo *mode, int width_mm, int height_mm) { int hactive, hblank, vactive, vblank, hsync_offset, hsync_pulse_width, vsync_offset, vsync_pulse_width; struct detailed_pixel_timing *pt = &dt->data.pixel_data; hactive = mode->hdisplay; hsync_offset = mode->hsync_start - mode->hdisplay; hsync_pulse_width = mode->hsync_end - mode->hsync_start; hblank = mode->htotal - mode->hdisplay; vactive = mode->vdisplay; vsync_offset = mode->vsync_start - mode->vdisplay; vsync_pulse_width = mode->vsync_end - mode->vsync_start; vblank = mode->vtotal - mode->vdisplay; dt->pixel_clock[0] = (mode->clock / 10) & 0x00FF; dt->pixel_clock[1] = ((mode->clock / 10) & 0xFF00) >> 8; assert(hactive <= 0xFFF); assert(hblank <= 0xFFF); pt->hactive_lo = hactive & 0x0FF; pt->hblank_lo = hblank & 0x0FF; pt->hactive_hblank_hi = (hactive & 0xF00) >> 4 | (hblank & 0xF00) >> 8; assert(vactive <= 0xFFF); assert(vblank <= 0xFFF); pt->vactive_lo = vactive & 0x0FF; pt->vblank_lo = vblank & 0x0FF; pt->vactive_vblank_hi = (vactive & 0xF00) >> 4 | (vblank & 0xF00) >> 8; assert(hsync_offset <= 0x3FF); assert(hsync_pulse_width <= 0x3FF); assert(vsync_offset <= 0x3F); assert(vsync_pulse_width <= 0x3F); pt->hsync_offset_lo = hsync_offset & 0x0FF; pt->hsync_pulse_width_lo = hsync_pulse_width & 0x0FF; pt->vsync_offset_pulse_width_lo = (vsync_offset & 0xF) << 4 | (vsync_pulse_width & 0xF); pt->hsync_vsync_offset_pulse_width_hi = ((hsync_offset & 0x300) >> 2) | ((hsync_pulse_width & 0x300) >> 4) | ((vsync_offset & 0x30) >> 2) | ((vsync_pulse_width & 0x30) >> 4); assert(width_mm <= 0xFFF); assert(height_mm <= 0xFFF); pt->width_mm_lo = width_mm & 0x0FF; pt->height_mm_lo = height_mm & 0x0FF; pt->width_height_mm_hi = (width_mm & 0xF00) >> 4 | (height_mm & 0xF00) >> 8; pt->misc = EDID_PT_SYNC_DIGITAL_SEPARATE; if (mode->flags & DRM_MODE_FLAG_PHSYNC) pt->misc |= EDID_PT_HSYNC_POSITIVE; if (mode->flags & DRM_MODE_FLAG_PVSYNC) pt->misc |= EDID_PT_VSYNC_POSITIVE; } /** * detailed_timing_set_monitor_range_mode: set a detailed timing to be a * monitor range based on a mode */ void detailed_timing_set_monitor_range_mode(struct detailed_timing *dt, drmModeModeInfo *mode) { struct detailed_non_pixel *np = &dt->data.other_data; struct detailed_data_monitor_range *mr = &np->data.range; dt->pixel_clock[0] = dt->pixel_clock[1] = 0; np->type = EDID_DETAIL_MONITOR_RANGE; mr->min_vfreq = mode->vrefresh - 1; mr->max_vfreq = mode->vrefresh + 1; mr->min_hfreq_khz = (mode->clock / mode->htotal) - 1; mr->max_hfreq_khz = (mode->clock / mode->htotal) + 1; mr->pixel_clock_mhz = (mode->clock / 10000) + 1; mr->flags = 0; memcpy(mr->formula.pad, monitor_range_padding, sizeof(monitor_range_padding)); } /** * detailed_timing_set_string: set a detailed timing to be a string */ void detailed_timing_set_string(struct detailed_timing *dt, enum detailed_non_pixel_type type, const char *str) { struct detailed_non_pixel *np = &dt->data.other_data; struct detailed_data_string *ds = &np->data.string; size_t len; switch (type) { case EDID_DETAIL_MONITOR_NAME: case EDID_DETAIL_MONITOR_STRING: case EDID_DETAIL_MONITOR_SERIAL: break; default: assert(0); /* not a string type */ } dt->pixel_clock[0] = dt->pixel_clock[1] = 0; np->type = type; len = min(strlen(str), sizeof(ds->str)); memcpy(ds->str, str, len); if (len < sizeof(ds->str)) ds->str[len++] = '\n'; while (len < sizeof(ds->str)) ds->str[len++] = ' '; } /** * edid_get_mfg: reads the 3-letter manufacturer identifier * * The string is *not* NULL-terminated. */ void edid_get_mfg(const struct edid *edid, char out[static 3]) { out[0] = ((edid->mfg_id[0] & 0x7C) >> 2) + '@'; out[1] = (((edid->mfg_id[0] & 0x03) << 3) | ((edid->mfg_id[1] & 0xE0) >> 5)) + '@'; out[2] = (edid->mfg_id[1] & 0x1F) + '@'; } static void edid_set_mfg(struct edid *edid, const char mfg[static 3]) { edid->mfg_id[0] = (mfg[0] - '@') << 2 | (mfg[1] - '@') >> 3; edid->mfg_id[1] = (mfg[1] - '@') << 5 | (mfg[2] - '@'); } static void edid_set_gamma(struct edid *edid, float gamma) { edid->gamma = (gamma * 100) - 100; } /** * edid_init: initialize an EDID * * The EDID will be pre-filled with established and standard timings: * * - 1920x1080 60Hz * - 1280x720 60Hz * - 1024x768 60Hz * - 800x600 60Hz * - 640x480 60Hz */ void edid_init(struct edid *edid) { size_t i; time_t t; struct tm *tm; memset(edid, 0, sizeof(struct edid)); memcpy(edid->header, edid_header, sizeof(edid_header)); edid_set_mfg(edid, "IGT"); edid->version = 1; edid->revision = 3; edid->input = 0x80; edid->width_cm = 52; edid->height_cm = 30; edid_set_gamma(edid, 2.20); edid->features = 0x02; /* Year of manufacture */ t = time(NULL); tm = localtime(&t); edid->mfg_year = tm->tm_year - 90; /* Established timings: 640x480 60Hz, 800x600 60Hz, 1024x768 60Hz */ edid->established_timings.t1 = 0x21; edid->established_timings.t2 = 0x08; /* Standard timings */ /* 1920x1080 60Hz */ std_timing_set(&edid->standard_timings[0], 1920, 60, STD_TIMING_16_9); /* 1280x720 60Hz */ std_timing_set(&edid->standard_timings[1], 1280, 60, STD_TIMING_16_9); /* 1024x768 60Hz */ std_timing_set(&edid->standard_timings[2], 1024, 60, STD_TIMING_4_3); /* 800x600 60Hz */ std_timing_set(&edid->standard_timings[3], 800, 60, STD_TIMING_4_3); /* 640x480 60Hz */ std_timing_set(&edid->standard_timings[4], 640, 60, STD_TIMING_4_3); for (i = 5; i < STD_TIMINGS_LEN; i++) std_timing_unset(&edid->standard_timings[i]); } /** * edid_init_with_mode: initialize an EDID and sets its preferred mode */ void edid_init_with_mode(struct edid *edid, drmModeModeInfo *mode) { edid_init(edid); /* Preferred timing */ detailed_timing_set_mode(&edid->detailed_timings[0], mode, edid->width_cm * 10, edid->height_cm * 10); detailed_timing_set_monitor_range_mode(&edid->detailed_timings[1], mode); detailed_timing_set_string(&edid->detailed_timings[2], EDID_DETAIL_MONITOR_NAME, "IGT"); } static uint8_t compute_checksum(const uint8_t *buf, size_t size) { size_t i; uint8_t sum = 0; assert(size > 0); for (i = 0; i < size - 1; i++) { sum += buf[i]; } return 256 - sum; } /** * edid_update_checksum: compute and update the checksums of the main EDID * block and all extension blocks */ void edid_update_checksum(struct edid *edid) { size_t i; struct edid_ext *ext; edid->checksum = compute_checksum((uint8_t *) edid, sizeof(struct edid)); for (i = 0; i < edid->extensions_len; i++) { ext = &edid->extensions[i]; if (ext->tag == EDID_EXT_CEA) ext->data.cea.checksum = compute_checksum((uint8_t *) ext, sizeof(struct edid_ext)); else if (ext->tag == EDID_EXT_DISPLAYID) { ext->data.tile.extension_checksum = compute_checksum((uint8_t *) &ext->data.tile, sizeof(struct edid_ext)); ext->data.tile.checksum = compute_checksum((uint8_t *) ext, sizeof(struct edid_ext)); } } } /** * base_edid_update_checksum: compute and update the checksum of the main EDID * block */ void base_edid_update_checksum(struct edid *edid) { edid->checksum = compute_checksum((uint8_t *) edid, sizeof(struct edid)); } /** * edid_get_size: return the size of the EDID block in bytes including EDID * extensions, if any. */ size_t edid_get_size(const struct edid *edid) { return sizeof(struct edid) + edid->extensions_len * sizeof(struct edid_ext); } static int ieee_oui(uint8_t oui[CEA_VSDB_HEADER_SIZE]) { return (oui[2] << 16) | (oui[1] << 8) | oui[0]; } /** * edid_get_deep_color_from_vsdb: return the Deep Color info from Vendor * Specific Data Block (VSDB), if VSDB not found then return zero. */ uint8_t edid_get_deep_color_from_vsdb(const struct edid *edid) { const struct edid_ext *edid_ext; const struct edid_cea *edid_cea; const char *cea_data; uint8_t deep_color = 0; int offset, i, j; /* * Read from vendor specific data block first, if vsdb not found * return 0. */ for (i = 0; i < edid->extensions_len; i++) { edid_ext = &edid->extensions[i]; edid_cea = &edid_ext->data.cea; if ((edid_ext->tag != EDID_EXT_CEA) || (edid_cea->revision != 3)) continue; offset = edid_cea->dtd_start; cea_data = edid_cea->data; for (j = 0; j < offset; j += (cea_data[j] & 0x1F) + 1) { struct edid_cea_data_block *vsdb = (struct edid_cea_data_block *)(cea_data + j); if (((vsdb->type_len & 0xE0) >> 5) != EDID_CEA_DATA_VENDOR_SPECIFIC) continue; if (ieee_oui(vsdb->data.vsdbs->ieee_oui) == 0x000C03) deep_color = vsdb->data.vsdbs->data.hdmi.flags1; if (deep_color & (7 << 4)) return deep_color; } } return 0; } /** * edid_get_bit_depth: Read from the Video Input Definition and return the * Color Bit Depth if Input is a Digital Video, else return zero. */ uint8_t edid_get_bit_depth_from_vid(const struct edid *edid) { /* * Video Signal Interface: Bit 7 (1:Digital, 0:Analog) * Color Bit Depth: Bits 6 → 4 */ if (!(edid->input & (1 << 7))) return 0; return ((edid->input & (7 << 4)) >> 4); } /** * cea_sad_init_pcm: * @channels: the number of supported channels (max. 8) * @sampling_rates: bitfield of enum cea_sad_sampling_rate * @sample_sizes: bitfield of enum cea_sad_pcm_sample_size * * Initialize a Short Audio Descriptor to advertise PCM support. */ void cea_sad_init_pcm(struct cea_sad *sad, int channels, uint8_t sampling_rates, uint8_t sample_sizes) { assert(channels <= 8); sad->format_channels = CEA_SAD_FORMAT_PCM << 3 | (channels - 1); sad->sampling_rates = sampling_rates; sad->bitrate = sample_sizes; } /** * cea_vsdb_get_hdmi_default: * * Returns the default Vendor Specific Data block for HDMI. */ const struct cea_vsdb *cea_vsdb_get_hdmi_default(size_t *size) { /* We'll generate a VSDB with 2 extension fields. */ static char raw[CEA_VSDB_HDMI_MIN_SIZE + 3] = {0}; struct cea_vsdb *vsdb; struct hdmi_vsdb *hdmi; *size = sizeof(raw); /* Magic incantation. Works better if you orient your screen in the * direction of the VESA headquarters. */ vsdb = (struct cea_vsdb *) raw; memcpy(vsdb->ieee_oui, hdmi_ieee_oui, sizeof(hdmi_ieee_oui)); hdmi = &vsdb->data.hdmi; hdmi->src_phy_addr[0] = 0x10; hdmi->src_phy_addr[1] = 0x00; /* 2 VSDB extension fields */ hdmi->flags1 = 0x38; hdmi->max_tdms_clock = 0x2D; return vsdb; } static void edid_cea_data_block_init(struct edid_cea_data_block *block, enum edid_cea_data_type type, size_t size) { assert(size <= 0xFF); block->type_len = type << 5 | size; } /** * edid_cea_data_block_set_sad: initialize a CEA data block to contain Short * Audio Descriptors */ size_t edid_cea_data_block_set_sad(struct edid_cea_data_block *block, const struct cea_sad *sads, size_t sads_len) { size_t sads_size; sads_size = sizeof(struct cea_sad) * sads_len; edid_cea_data_block_init(block, EDID_CEA_DATA_AUDIO, sads_size); memcpy(block->data.sads, sads, sads_size); return sizeof(struct edid_cea_data_block) + sads_size; } /** * edid_cea_data_block_set_svd: initialize a CEA data block to contain Short * Video Descriptors */ size_t edid_cea_data_block_set_svd(struct edid_cea_data_block *block, const uint8_t *svds, size_t svds_len) { edid_cea_data_block_init(block, EDID_CEA_DATA_VIDEO, svds_len); memcpy(block->data.svds, svds, svds_len); return sizeof(struct edid_cea_data_block) + svds_len; } /** * edid_cea_data_block_set_vsdb: initialize a CEA data block to contain a * Vendor Specific Data Block */ size_t edid_cea_data_block_set_vsdb(struct edid_cea_data_block *block, const struct cea_vsdb *vsdb, size_t vsdb_size) { edid_cea_data_block_init(block, EDID_CEA_DATA_VENDOR_SPECIFIC, vsdb_size); memcpy(block->data.vsdbs, vsdb, vsdb_size); return sizeof(struct edid_cea_data_block) + vsdb_size; } /** * edid_cea_data_block_set_hdmi_vsdb: initialize a CEA data block to contain an * HDMI VSDB */ size_t edid_cea_data_block_set_hdmi_vsdb(struct edid_cea_data_block *block, const struct hdmi_vsdb *hdmi, size_t hdmi_size) { char raw_vsdb[CEA_VSDB_HDMI_MAX_SIZE] = {0}; struct cea_vsdb *vsdb = (struct cea_vsdb *) raw_vsdb; assert(hdmi_size >= HDMI_VSDB_MIN_SIZE && hdmi_size <= HDMI_VSDB_MAX_SIZE); memcpy(vsdb->ieee_oui, hdmi_ieee_oui, sizeof(hdmi_ieee_oui)); memcpy(&vsdb->data.hdmi, hdmi, hdmi_size); return edid_cea_data_block_set_vsdb(block, vsdb, CEA_VSDB_HEADER_SIZE + hdmi_size); } /** * edid_cea_data_block_set_speaker_alloc: initialize a CEA data block to * contain a Speaker Allocation Data block */ size_t edid_cea_data_block_set_speaker_alloc(struct edid_cea_data_block *block, const struct cea_speaker_alloc *speakers) { size_t size; size = sizeof(struct cea_speaker_alloc); edid_cea_data_block_init(block, EDID_CEA_DATA_SPEAKER_ALLOC, size); memcpy(block->data.speakers, speakers, size); return sizeof(struct edid_cea_data_block) + size; } /** * edid_ext_set_tile initialize an EDID extension block to be identified * as a tiled display topology block */ void edid_ext_set_displayid(struct edid_ext *ext) { ext->tag = EDID_EXT_DISPLAYID; } /** * edid_ext_set_cea: initialize an EDID extension block to contain a CEA * extension. CEA extensions contain a Data Block Collection (with multiple * CEA data blocks) followed by multiple Detailed Timing Descriptors. */ void edid_ext_set_cea(struct edid_ext *ext, size_t data_blocks_size, uint8_t num_native_dtds, uint8_t flags) { struct edid_cea *cea = &ext->data.cea; ext->tag = EDID_EXT_CEA; assert(num_native_dtds <= 0x0F); assert((flags & 0x0F) == 0); assert(data_blocks_size <= sizeof(cea->data)); cea->revision = 3; cea->dtd_start = 4 + data_blocks_size; cea->misc = flags | num_native_dtds; }