2022-06-06 20:53:10 +02:00
|
|
|
#include <assert.h>
|
2022-03-24 13:23:07 +01:00
|
|
|
#include <errno.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2022-06-11 11:46:10 +02:00
|
|
|
#include "bits.h"
|
2022-06-22 13:06:37 +02:00
|
|
|
#include "dmt.h"
|
2022-03-24 13:23:07 +01:00
|
|
|
#include "edid.h"
|
2022-08-02 12:18:19 +02:00
|
|
|
#include "log.h"
|
2022-03-24 13:23:07 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The size of an EDID block, defined in section 2.2.
|
|
|
|
*/
|
|
|
|
#define EDID_BLOCK_SIZE 128
|
2022-06-20 16:41:09 +02:00
|
|
|
/**
|
|
|
|
* The size of an EDID standard timing, defined in section 3.9.
|
|
|
|
*/
|
|
|
|
#define EDID_STANDARD_TIMING_SIZE 2
|
2022-09-28 02:46:42 +02:00
|
|
|
/**
|
|
|
|
* The size of an EDID CVT timing code, defined in section 3.10.3.8.
|
|
|
|
*/
|
|
|
|
#define EDID_CVT_TIMING_CODE_SIZE 3
|
2022-03-24 13:23:07 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Fixed EDID header, defined in section 3.1.
|
|
|
|
*/
|
|
|
|
static const uint8_t header[] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 };
|
|
|
|
|
2022-06-22 15:29:53 +02:00
|
|
|
static void
|
|
|
|
add_failure(struct di_edid *edid, const char fmt[], ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
va_start(args, fmt);
|
2022-08-02 12:18:19 +02:00
|
|
|
_di_logger_va_add_failure(edid->logger, fmt, args);
|
2022-06-22 15:29:53 +02:00
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
add_failure_until(struct di_edid *edid, int revision, const char fmt[], ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
if (edid->revision > revision) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
va_start(args, fmt);
|
2022-08-02 12:18:19 +02:00
|
|
|
_di_logger_va_add_failure(edid->logger, fmt, args);
|
2022-06-22 15:29:53 +02:00
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
2022-03-24 13:23:07 +01:00
|
|
|
static void
|
|
|
|
parse_version_revision(const uint8_t data[static EDID_BLOCK_SIZE],
|
|
|
|
int *version, int *revision)
|
|
|
|
{
|
2022-05-12 10:59:33 +02:00
|
|
|
*version = (int) data[0x12];
|
|
|
|
*revision = (int) data[0x13];
|
2022-03-24 13:23:07 +01:00
|
|
|
}
|
|
|
|
|
2022-05-25 17:32:25 +02:00
|
|
|
static size_t
|
|
|
|
parse_ext_count(const uint8_t data[static EDID_BLOCK_SIZE])
|
|
|
|
{
|
|
|
|
return data[0x7E];
|
|
|
|
}
|
|
|
|
|
2022-03-24 13:23:07 +01:00
|
|
|
static bool
|
|
|
|
validate_block_checksum(const uint8_t data[static EDID_BLOCK_SIZE])
|
|
|
|
{
|
|
|
|
uint8_t sum = 0;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < EDID_BLOCK_SIZE; i++) {
|
|
|
|
sum += data[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
return sum == 0;
|
|
|
|
}
|
|
|
|
|
2022-03-24 17:54:29 +01:00
|
|
|
static void
|
2022-06-22 09:19:02 +02:00
|
|
|
parse_vendor_product(struct di_edid *edid,
|
|
|
|
const uint8_t data[static EDID_BLOCK_SIZE])
|
2022-03-24 17:54:29 +01:00
|
|
|
{
|
2022-06-22 09:19:02 +02:00
|
|
|
struct di_edid_vendor_product *out = &edid->vendor_product;
|
|
|
|
uint16_t man, raw_week, raw_year;
|
2022-03-24 17:54:29 +01:00
|
|
|
int year = 0;
|
|
|
|
|
|
|
|
/* The ASCII 3-letter manufacturer code is encoded in 5-bit codes. */
|
2022-05-12 10:59:33 +02:00
|
|
|
man = (uint16_t) ((data[0x08] << 8) | data[0x09]);
|
2022-03-24 17:54:29 +01:00
|
|
|
out->manufacturer[0] = ((man >> 10) & 0x1F) + '@';
|
|
|
|
out->manufacturer[1] = ((man >> 5) & 0x1F) + '@';
|
|
|
|
out->manufacturer[2] = ((man >> 0) & 0x1F) + '@';
|
|
|
|
|
2022-05-12 10:59:33 +02:00
|
|
|
out->product = (uint16_t) (data[0x0A] | (data[0x0B] << 8));
|
|
|
|
out->serial = (uint32_t) (data[0x0C] |
|
|
|
|
(data[0x0D] << 8) |
|
|
|
|
(data[0x0E] << 16) |
|
|
|
|
(data[0x0F] << 24));
|
2022-03-24 17:54:29 +01:00
|
|
|
|
2022-06-22 09:19:02 +02:00
|
|
|
raw_week = data[0x10];
|
|
|
|
raw_year = data[0x11];
|
|
|
|
|
|
|
|
if (raw_year >= 0x10 || edid->revision < 4) {
|
2022-05-12 10:59:33 +02:00
|
|
|
year = data[0x11] + 1990;
|
2022-06-22 09:19:02 +02:00
|
|
|
} else if (edid->revision == 4) {
|
|
|
|
add_failure(edid, "Year set to reserved value.");
|
2022-03-24 17:54:29 +01:00
|
|
|
}
|
|
|
|
|
2022-06-22 09:19:02 +02:00
|
|
|
if (raw_week == 0xFF) {
|
2022-03-24 17:54:29 +01:00
|
|
|
/* Special flag for model year */
|
|
|
|
out->model_year = year;
|
|
|
|
} else {
|
|
|
|
out->manufacture_year = year;
|
2022-06-22 09:19:02 +02:00
|
|
|
if (raw_week > 54) {
|
|
|
|
add_failure_until(edid, 4,
|
|
|
|
"Invalid week %u of manufacture.",
|
|
|
|
raw_week);
|
|
|
|
} else if (raw_week > 0) {
|
|
|
|
out->manufacture_week = raw_week;
|
2022-03-24 17:54:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-02 22:16:14 +02:00
|
|
|
static void
|
2022-05-10 12:53:52 +02:00
|
|
|
parse_video_input_digital(struct di_edid *edid, uint8_t video_input)
|
2022-05-10 12:20:55 +02:00
|
|
|
{
|
2022-05-10 12:53:52 +02:00
|
|
|
uint8_t color_bit_depth, interface;
|
2022-05-10 12:20:55 +02:00
|
|
|
struct di_edid_video_input_digital *digital = &edid->video_input_digital;
|
|
|
|
|
2022-08-03 11:03:22 +02:00
|
|
|
if (edid->revision < 2) {
|
|
|
|
if (get_bit_range(video_input, 6, 0) != 0)
|
|
|
|
add_failure(edid, "Digital Video Interface Standard set to reserved value 0x%02x.",
|
|
|
|
video_input);
|
|
|
|
return;
|
|
|
|
}
|
2022-05-10 12:53:52 +02:00
|
|
|
if (edid->revision < 4) {
|
2022-08-03 11:03:22 +02:00
|
|
|
if (get_bit_range(video_input, 6, 1) != 0)
|
|
|
|
add_failure(edid, "Digital Video Interface Standard set to reserved value 0x%02x.",
|
|
|
|
video_input);
|
|
|
|
digital->dfp1 = has_bit(video_input, 0);
|
2022-08-02 22:16:14 +02:00
|
|
|
return;
|
2022-05-10 12:20:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
color_bit_depth = get_bit_range(video_input, 6, 4);
|
|
|
|
if (color_bit_depth == 0x07) {
|
|
|
|
/* Reserved */
|
2022-06-29 12:21:04 +02:00
|
|
|
add_failure_until(edid, 4, "Color Bit Depth set to reserved value.");
|
2022-05-10 12:20:55 +02:00
|
|
|
} else if (color_bit_depth != 0) {
|
|
|
|
digital->color_bit_depth = 2 * color_bit_depth + 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface = get_bit_range(video_input, 3, 0);
|
|
|
|
switch (interface) {
|
|
|
|
case DI_EDID_VIDEO_INPUT_DIGITAL_UNDEFINED:
|
|
|
|
case DI_EDID_VIDEO_INPUT_DIGITAL_DVI:
|
|
|
|
case DI_EDID_VIDEO_INPUT_DIGITAL_HDMI_A:
|
|
|
|
case DI_EDID_VIDEO_INPUT_DIGITAL_HDMI_B:
|
|
|
|
case DI_EDID_VIDEO_INPUT_DIGITAL_MDDI:
|
|
|
|
case DI_EDID_VIDEO_INPUT_DIGITAL_DISPLAYPORT:
|
|
|
|
digital->interface = interface;
|
|
|
|
break;
|
|
|
|
default:
|
2022-06-29 12:21:04 +02:00
|
|
|
add_failure_until(edid, 4,
|
|
|
|
"Digital Video Interface Standard set to reserved value 0x%02x.",
|
|
|
|
interface);
|
2022-05-10 12:20:55 +02:00
|
|
|
digital->interface = DI_EDID_VIDEO_INPUT_DIGITAL_UNDEFINED;
|
2022-06-29 12:21:04 +02:00
|
|
|
break;
|
2022-05-10 12:20:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-03 11:40:37 +02:00
|
|
|
static void
|
|
|
|
parse_video_input_analog(struct di_edid *edid, uint8_t video_input)
|
|
|
|
{
|
|
|
|
struct di_edid_video_input_analog *analog = &edid->video_input_analog;
|
|
|
|
|
|
|
|
analog->signal_level_std = get_bit_range(video_input, 6, 5);
|
|
|
|
analog->video_setup = has_bit(video_input, 4);
|
|
|
|
analog->sync_separate = has_bit(video_input, 3);
|
|
|
|
analog->sync_composite = has_bit(video_input, 2);
|
|
|
|
analog->sync_on_green = has_bit(video_input, 1);
|
|
|
|
analog->sync_serrations = has_bit(video_input, 0);
|
|
|
|
}
|
|
|
|
|
2022-08-02 22:16:14 +02:00
|
|
|
static void
|
2022-05-10 12:53:52 +02:00
|
|
|
parse_basic_params_features(struct di_edid *edid,
|
|
|
|
const uint8_t data[static EDID_BLOCK_SIZE])
|
|
|
|
{
|
2022-05-12 11:30:54 +02:00
|
|
|
uint8_t video_input, width, height, features;
|
2022-05-10 12:53:52 +02:00
|
|
|
struct di_edid_screen_size *screen_size = &edid->screen_size;
|
|
|
|
|
|
|
|
video_input = data[0x14];
|
|
|
|
edid->is_digital = has_bit(video_input, 7);
|
|
|
|
|
|
|
|
if (edid->is_digital) {
|
2022-08-02 22:16:14 +02:00
|
|
|
parse_video_input_digital(edid, video_input);
|
2022-08-03 11:40:37 +02:00
|
|
|
} else {
|
|
|
|
parse_video_input_analog(edid, video_input);
|
2022-05-10 12:53:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* v1.3 says screen size is undefined if either byte is zero, v1.4 says
|
|
|
|
* screen size and aspect ratio are undefined if both bytes are zero and
|
|
|
|
* encodes the aspect ratio if either byte is zero. */
|
|
|
|
width = data[0x15];
|
|
|
|
height = data[0x16];
|
|
|
|
if (width > 0 && height > 0) {
|
|
|
|
screen_size->width_cm = width;
|
|
|
|
screen_size->height_cm = height;
|
|
|
|
} else if (edid->revision >= 4) {
|
|
|
|
if (width > 0) {
|
|
|
|
screen_size->landscape_aspect_ratio = ((float) width + 99) / 100;
|
|
|
|
} else if (height > 0) {
|
|
|
|
screen_size->portait_aspect_ratio = ((float) height + 99) / 100;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:15:26 +02:00
|
|
|
if (data[0x17] != 0xFF) {
|
|
|
|
edid->gamma = ((float) data[0x17] + 100) / 100;
|
|
|
|
} else {
|
|
|
|
edid->gamma = 0;
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:30:54 +02:00
|
|
|
features = data[0x18];
|
|
|
|
|
|
|
|
edid->dpms.standby = has_bit(features, 7);
|
|
|
|
edid->dpms.suspend = has_bit(features, 6);
|
|
|
|
edid->dpms.off = has_bit(features, 5);
|
|
|
|
|
2022-05-13 10:07:13 +02:00
|
|
|
if (edid->is_digital && edid->revision >= 4) {
|
|
|
|
edid->color_encoding_formats.rgb444 = true;
|
|
|
|
edid->color_encoding_formats.ycrcb444 = has_bit(features, 3);
|
|
|
|
edid->color_encoding_formats.ycrcb422 = has_bit(features, 4);
|
2022-06-17 13:35:24 +02:00
|
|
|
edid->display_color_type = DI_EDID_DISPLAY_COLOR_UNDEFINED;
|
|
|
|
} else {
|
|
|
|
edid->display_color_type = get_bit_range(features, 4, 3);
|
2022-05-13 10:07:13 +02:00
|
|
|
}
|
|
|
|
|
2022-05-13 10:42:59 +02:00
|
|
|
if (edid->revision >= 4) {
|
|
|
|
edid->misc_features.has_preferred_timing = true;
|
|
|
|
edid->misc_features.continuous_freq = has_bit(features, 0);
|
|
|
|
edid->misc_features.preferred_timing_is_native = has_bit(features, 1);
|
|
|
|
} else {
|
|
|
|
edid->misc_features.default_gtf = has_bit(features, 0);
|
|
|
|
edid->misc_features.has_preferred_timing = has_bit(features, 1);
|
|
|
|
}
|
|
|
|
edid->misc_features.srgb_is_primary = has_bit(features, 2);
|
2022-05-10 12:53:52 +02:00
|
|
|
}
|
|
|
|
|
2022-06-06 12:03:15 +02:00
|
|
|
static float
|
|
|
|
decode_chromaticity_coord(uint8_t hi, uint8_t lo)
|
|
|
|
{
|
|
|
|
uint16_t raw; /* only 10 bits are used */
|
|
|
|
|
2022-10-13 12:03:16 +02:00
|
|
|
raw = (uint16_t) ((hi << 2) | lo);
|
2022-06-06 12:03:15 +02:00
|
|
|
return (float) raw / 1024;
|
|
|
|
}
|
|
|
|
|
2022-08-02 22:17:18 +02:00
|
|
|
static void
|
2022-06-29 12:21:04 +02:00
|
|
|
parse_chromaticity_coords(struct di_edid *edid,
|
|
|
|
const uint8_t data[static EDID_BLOCK_SIZE])
|
2022-06-06 12:03:15 +02:00
|
|
|
{
|
|
|
|
uint8_t lo;
|
|
|
|
bool all_set, any_set;
|
2022-06-29 12:21:04 +02:00
|
|
|
struct di_edid_chromaticity_coords *coords;
|
|
|
|
|
|
|
|
coords = &edid->chromaticity_coords;
|
2022-06-06 12:03:15 +02:00
|
|
|
|
|
|
|
lo = data[0x19];
|
|
|
|
coords->red_x = decode_chromaticity_coord(data[0x1B], get_bit_range(lo, 7, 6));
|
|
|
|
coords->red_y = decode_chromaticity_coord(data[0x1C], get_bit_range(lo, 5, 4));
|
|
|
|
coords->green_x = decode_chromaticity_coord(data[0x1D], get_bit_range(lo, 3, 2));
|
|
|
|
coords->green_y = decode_chromaticity_coord(data[0x1E], get_bit_range(lo, 1, 0));
|
|
|
|
|
|
|
|
lo = data[0x1A];
|
|
|
|
coords->blue_x = decode_chromaticity_coord(data[0x1F], get_bit_range(lo, 7, 6));
|
|
|
|
coords->blue_y = decode_chromaticity_coord(data[0x20], get_bit_range(lo, 5, 4));
|
|
|
|
coords->white_x = decode_chromaticity_coord(data[0x21], get_bit_range(lo, 3, 2));
|
|
|
|
coords->white_y = decode_chromaticity_coord(data[0x22], get_bit_range(lo, 1, 0));
|
|
|
|
|
|
|
|
/* Either all primaries coords must be set, either none must be set */
|
|
|
|
any_set = coords->red_x != 0 || coords->red_y != 0
|
|
|
|
|| coords->green_x != 0 || coords->green_y != 0
|
|
|
|
|| coords->blue_x != 0 || coords->blue_y != 0;
|
|
|
|
all_set = coords->red_x != 0 && coords->red_y != 0
|
|
|
|
&& coords->green_x != 0 && coords->green_y != 0
|
|
|
|
&& coords->blue_x != 0 && coords->blue_y != 0;
|
|
|
|
if (any_set && !all_set) {
|
2022-06-29 12:21:04 +02:00
|
|
|
add_failure(edid, "Some but not all primaries coordinates are unset.");
|
2022-06-06 12:03:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Both white-point coords must be set */
|
|
|
|
if (coords->white_x == 0 || coords->white_y == 0) {
|
2022-06-29 12:21:04 +02:00
|
|
|
add_failure(edid, "White-point coordinates are unset.");
|
2022-06-06 12:03:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-02 17:42:57 +02:00
|
|
|
static void
|
|
|
|
parse_established_timings_i_ii(struct di_edid *edid,
|
|
|
|
const uint8_t data[static EDID_BLOCK_SIZE])
|
|
|
|
{
|
|
|
|
struct di_edid_established_timings_i_ii *timings = &edid->established_timings_i_ii;
|
|
|
|
|
|
|
|
timings->has_720x400_70hz = has_bit(data[0x23], 7);
|
|
|
|
timings->has_720x400_88hz = has_bit(data[0x23], 6);
|
|
|
|
timings->has_640x480_60hz = has_bit(data[0x23], 5);
|
|
|
|
timings->has_640x480_67hz = has_bit(data[0x23], 4);
|
|
|
|
timings->has_640x480_72hz = has_bit(data[0x23], 3);
|
|
|
|
timings->has_640x480_75hz = has_bit(data[0x23], 2);
|
|
|
|
timings->has_800x600_56hz = has_bit(data[0x23], 1);
|
|
|
|
timings->has_800x600_60hz = has_bit(data[0x23], 0);
|
|
|
|
|
|
|
|
/* Established timings II */
|
|
|
|
timings->has_800x600_72hz = has_bit(data[0x24], 7);
|
|
|
|
timings->has_800x600_75hz = has_bit(data[0x24], 6);
|
|
|
|
timings->has_832x624_75hz = has_bit(data[0x24], 5);
|
|
|
|
timings->has_1024x768_87hz_interlaced = has_bit(data[0x24], 4);
|
|
|
|
timings->has_1024x768_60hz = has_bit(data[0x24], 3);
|
|
|
|
timings->has_1024x768_70hz = has_bit(data[0x24], 2);
|
|
|
|
timings->has_1024x768_75hz = has_bit(data[0x24], 1);
|
|
|
|
timings->has_1280x1024_75hz = has_bit(data[0x24], 0);
|
|
|
|
|
|
|
|
timings->has_1152x870_75hz = has_bit(data[0x25], 7);
|
|
|
|
/* TODO: manufacturer specified timings in bits 6:0 */
|
|
|
|
}
|
|
|
|
|
2022-06-20 16:41:09 +02:00
|
|
|
static bool
|
|
|
|
parse_standard_timing(struct di_edid *edid,
|
2022-08-15 18:53:16 +02:00
|
|
|
const uint8_t data[static EDID_STANDARD_TIMING_SIZE],
|
|
|
|
struct di_edid_standard_timing **out)
|
2022-06-20 16:41:09 +02:00
|
|
|
{
|
|
|
|
struct di_edid_standard_timing *t;
|
|
|
|
|
2022-08-15 18:53:16 +02:00
|
|
|
*out = NULL;
|
|
|
|
|
2022-06-20 16:41:09 +02:00
|
|
|
if (data[0] == 0x01 && data[1] == 0x01) {
|
|
|
|
/* Unused */
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (data[0] == 0x00) {
|
2022-06-29 12:21:04 +02:00
|
|
|
add_failure_until(edid, 4,
|
|
|
|
"Use 0x0101 as the invalid Standard Timings code, not 0x%02x%02x.",
|
|
|
|
data[0], data[1]);
|
|
|
|
return true;
|
2022-06-20 16:41:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
t = calloc(1, sizeof(*t));
|
|
|
|
if (!t) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
t->horiz_video = ((int32_t) data[0] + 31) * 8;
|
|
|
|
t->aspect_ratio = get_bit_range(data[1], 7, 6);
|
|
|
|
t->refresh_rate_hz = (int32_t) get_bit_range(data[1], 5, 0) + 60;
|
|
|
|
|
2022-08-15 18:53:16 +02:00
|
|
|
*out = t;
|
2022-06-20 16:41:09 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-08-17 00:47:08 +02:00
|
|
|
struct di_edid_detailed_timing_def_priv *
|
2022-08-02 18:56:02 +02:00
|
|
|
_di_edid_parse_detailed_timing_def(const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE])
|
2022-06-10 18:19:38 +02:00
|
|
|
{
|
2022-08-17 00:47:08 +02:00
|
|
|
struct di_edid_detailed_timing_def_priv *priv;
|
2022-06-10 18:19:38 +02:00
|
|
|
struct di_edid_detailed_timing_def *def;
|
2022-08-17 00:47:08 +02:00
|
|
|
struct di_edid_detailed_timing_analog_composite *analog_composite;
|
|
|
|
struct di_edid_detailed_timing_bipolar_analog_composite *bipolar_analog_composite;
|
|
|
|
struct di_edid_detailed_timing_digital_composite *digital_composite;
|
|
|
|
struct di_edid_detailed_timing_digital_separate *digital_separate;
|
2022-06-10 18:19:38 +02:00
|
|
|
int raw;
|
2022-06-17 11:22:09 +02:00
|
|
|
uint8_t flags, stereo_hi, stereo_lo;
|
2022-06-10 18:19:38 +02:00
|
|
|
|
2022-08-17 00:47:08 +02:00
|
|
|
priv = calloc(1, sizeof(*priv));
|
|
|
|
if (!priv) {
|
2022-08-02 18:56:02 +02:00
|
|
|
return NULL;
|
2022-06-10 18:19:38 +02:00
|
|
|
}
|
|
|
|
|
2022-08-17 00:47:08 +02:00
|
|
|
def = &priv->base;
|
|
|
|
|
2022-06-10 18:19:38 +02:00
|
|
|
raw = (data[1] << 8) | data[0];
|
|
|
|
def->pixel_clock_hz = raw * 10 * 1000;
|
|
|
|
|
|
|
|
def->horiz_video = (get_bit_range(data[4], 7, 4) << 8) | data[2];
|
|
|
|
def->horiz_blank = (get_bit_range(data[4], 3, 0) << 8) | data[3];
|
|
|
|
|
|
|
|
def->vert_video = (get_bit_range(data[7], 7, 4) << 8) | data[5];
|
|
|
|
def->vert_blank = (get_bit_range(data[7], 3, 0) << 8) | data[6];
|
|
|
|
|
|
|
|
def->horiz_front_porch = (get_bit_range(data[11], 7, 6) << 8) | data[8];
|
|
|
|
def->horiz_sync_pulse = (get_bit_range(data[11], 5, 4) << 8) | data[9];
|
|
|
|
def->vert_front_porch = (get_bit_range(data[11], 3, 2) << 4)
|
|
|
|
| get_bit_range(data[10], 7, 4);
|
|
|
|
def->vert_sync_pulse = (get_bit_range(data[11], 1, 0) << 4)
|
|
|
|
| get_bit_range(data[10], 3, 0);
|
|
|
|
|
|
|
|
def->horiz_image_mm = (get_bit_range(data[14], 7, 4) << 8) | data[12];
|
|
|
|
def->vert_image_mm = (get_bit_range(data[14], 3, 0) << 8) | data[13];
|
2022-06-17 09:32:14 +02:00
|
|
|
if ((def->horiz_image_mm == 16 && def->vert_image_mm == 9)
|
|
|
|
|| (def->horiz_image_mm == 4 && def->vert_image_mm == 3)) {
|
|
|
|
/* Table 3.21 note 18.2: these are special cases and define the
|
|
|
|
* aspect ratio rather than the size in mm.
|
|
|
|
* TODO: expose these values */
|
|
|
|
def->horiz_image_mm = def->vert_image_mm = 0;
|
|
|
|
}
|
2022-06-10 18:19:38 +02:00
|
|
|
|
|
|
|
def->horiz_border = data[15];
|
|
|
|
def->vert_border = data[16];
|
|
|
|
|
2022-06-17 11:22:09 +02:00
|
|
|
flags = data[17];
|
|
|
|
|
|
|
|
def->interlaced = has_bit(flags, 7);
|
|
|
|
|
|
|
|
stereo_hi = get_bit_range(flags, 6, 5);
|
|
|
|
stereo_lo = get_bit_range(flags, 0, 0);
|
|
|
|
if (stereo_hi == 0) {
|
|
|
|
def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_NONE;
|
|
|
|
} else {
|
|
|
|
switch ((stereo_hi << 1) | stereo_lo) {
|
|
|
|
case (1 << 1) | 0:
|
|
|
|
def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_FIELD_SEQ_RIGHT;
|
|
|
|
break;
|
|
|
|
case (2 << 1) | 0:
|
|
|
|
def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_FIELD_SEQ_LEFT;
|
|
|
|
break;
|
|
|
|
case (1 << 1) | 1:
|
|
|
|
def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_2_WAY_INTERLEAVED_RIGHT;
|
|
|
|
break;
|
|
|
|
case (2 << 1) | 1:
|
|
|
|
def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_2_WAY_INTERLEAVED_LEFT;
|
|
|
|
break;
|
|
|
|
case (3 << 1) | 0:
|
|
|
|
def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_4_WAY_INTERLEAVED;
|
|
|
|
break;
|
|
|
|
case (3 << 1) | 1:
|
|
|
|
def->stereo = DI_EDID_DETAILED_TIMING_DEF_STEREO_SIDE_BY_SIDE_INTERLEAVED;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
abort(); /* unreachable */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-22 09:22:15 +02:00
|
|
|
def->signal_type = get_bit_range(flags, 4, 3);
|
|
|
|
|
2022-06-22 09:45:59 +02:00
|
|
|
switch (def->signal_type) {
|
|
|
|
case DI_EDID_DETAILED_TIMING_DEF_SIGNAL_ANALOG_COMPOSITE:
|
2022-08-17 00:47:08 +02:00
|
|
|
analog_composite = &priv->analog_composite;
|
|
|
|
analog_composite->sync_serrations = has_bit(flags, 2);
|
2023-01-30 17:09:28 +01:00
|
|
|
analog_composite->sync_on_green = !has_bit(flags, 1);
|
2022-08-17 00:47:08 +02:00
|
|
|
def->analog_composite = analog_composite;
|
2022-06-22 09:45:59 +02:00
|
|
|
break;
|
|
|
|
case DI_EDID_DETAILED_TIMING_DEF_SIGNAL_BIPOLAR_ANALOG_COMPOSITE:
|
2022-08-17 00:47:08 +02:00
|
|
|
bipolar_analog_composite = &priv->bipolar_analog_composite;
|
|
|
|
bipolar_analog_composite->sync_serrations = has_bit(flags, 2);
|
2023-01-30 17:09:28 +01:00
|
|
|
bipolar_analog_composite->sync_on_green = !has_bit(flags, 1);
|
2022-08-17 00:47:08 +02:00
|
|
|
def->bipolar_analog_composite = bipolar_analog_composite;
|
2022-06-22 09:45:59 +02:00
|
|
|
break;
|
|
|
|
case DI_EDID_DETAILED_TIMING_DEF_SIGNAL_DIGITAL_COMPOSITE:
|
2022-08-17 00:47:08 +02:00
|
|
|
digital_composite = &priv->digital_composite;
|
|
|
|
digital_composite->sync_serrations = has_bit(flags, 2);
|
|
|
|
digital_composite->sync_horiz_polarity = has_bit(flags, 1);
|
|
|
|
def->digital_composite = digital_composite;
|
2022-06-22 09:45:59 +02:00
|
|
|
break;
|
|
|
|
case DI_EDID_DETAILED_TIMING_DEF_SIGNAL_DIGITAL_SEPARATE:
|
2022-08-17 00:47:08 +02:00
|
|
|
digital_separate = &priv->digital_separate;
|
|
|
|
digital_separate->sync_vert_polarity = has_bit(flags, 2);
|
|
|
|
digital_separate->sync_horiz_polarity = has_bit(flags, 1);
|
|
|
|
def->digital_separate = digital_separate;
|
2022-06-22 09:45:59 +02:00
|
|
|
break;
|
|
|
|
}
|
2022-06-10 18:19:38 +02:00
|
|
|
|
2022-08-17 00:47:08 +02:00
|
|
|
return priv;
|
2022-06-10 18:19:38 +02:00
|
|
|
}
|
|
|
|
|
2022-06-06 23:54:06 +02:00
|
|
|
static bool
|
2022-06-29 12:21:04 +02:00
|
|
|
decode_display_range_limits_offset(struct di_edid *edid, uint8_t flags,
|
2022-06-06 23:54:06 +02:00
|
|
|
int *max_offset, int *min_offset)
|
|
|
|
{
|
|
|
|
switch (flags) {
|
|
|
|
case 0x00:
|
|
|
|
/* No offset */
|
|
|
|
break;
|
|
|
|
case 0x02:
|
|
|
|
*max_offset = 255;
|
|
|
|
break;
|
|
|
|
case 0x03:
|
|
|
|
*max_offset = 255;
|
|
|
|
*min_offset = 255;
|
|
|
|
break;
|
|
|
|
default:
|
2022-06-29 12:21:04 +02:00
|
|
|
add_failure_until(edid, 4,
|
|
|
|
"Range offset flags set to reserved value 0x%02x.",
|
|
|
|
flags);
|
|
|
|
return false;
|
2022-06-06 23:54:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
2022-06-29 12:21:04 +02:00
|
|
|
parse_display_range_limits(struct di_edid *edid,
|
2022-06-06 23:54:06 +02:00
|
|
|
const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE],
|
2022-08-08 19:34:01 +02:00
|
|
|
struct di_edid_display_range_limits_priv *priv)
|
2022-06-06 23:54:06 +02:00
|
|
|
{
|
|
|
|
uint8_t offset_flags, vert_offset_flags, horiz_offset_flags;
|
2022-08-09 10:22:38 +02:00
|
|
|
uint8_t support_flags, preferred_aspect_ratio;
|
2022-06-06 23:54:06 +02:00
|
|
|
int max_vert_offset = 0, min_vert_offset = 0;
|
|
|
|
int max_horiz_offset = 0, min_horiz_offset = 0;
|
2022-08-08 19:20:51 +02:00
|
|
|
size_t i;
|
2022-08-08 19:34:01 +02:00
|
|
|
struct di_edid_display_range_limits *base;
|
|
|
|
struct di_edid_display_range_limits_secondary_gtf *secondary_gtf;
|
2022-08-09 10:22:38 +02:00
|
|
|
struct di_edid_display_range_limits_cvt *cvt;
|
2022-08-08 19:34:01 +02:00
|
|
|
|
|
|
|
base = &priv->base;
|
2022-06-06 23:54:06 +02:00
|
|
|
|
|
|
|
offset_flags = data[4];
|
|
|
|
if (edid->revision >= 4) {
|
|
|
|
vert_offset_flags = get_bit_range(offset_flags, 1, 0);
|
|
|
|
horiz_offset_flags = get_bit_range(offset_flags, 3, 2);
|
|
|
|
|
|
|
|
if (!decode_display_range_limits_offset(edid,
|
|
|
|
vert_offset_flags,
|
|
|
|
&max_vert_offset,
|
|
|
|
&min_vert_offset)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!decode_display_range_limits_offset(edid,
|
|
|
|
horiz_offset_flags,
|
|
|
|
&max_horiz_offset,
|
|
|
|
&min_horiz_offset)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (edid->revision <= 4 &&
|
|
|
|
get_bit_range(offset_flags, 7, 4) != 0) {
|
2022-08-08 09:48:04 +02:00
|
|
|
add_failure(edid, "Display Range Limits: Bits 7:4 of the range offset flags are reserved.");
|
2022-06-06 23:54:06 +02:00
|
|
|
}
|
|
|
|
} else if (offset_flags != 0) {
|
2022-08-08 09:48:04 +02:00
|
|
|
add_failure(edid, "Display Range Limits: Range offset flags are unsupported in EDID 1.3.");
|
2022-06-06 23:54:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (edid->revision <= 4 && (data[5] == 0 || data[6] == 0 ||
|
|
|
|
data[7] == 0 || data[8] == 0)) {
|
2022-08-08 09:48:04 +02:00
|
|
|
add_failure(edid, "Display Range Limits: Range limits set to reserved values.");
|
2022-06-06 23:54:06 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-08-08 19:34:01 +02:00
|
|
|
base->min_vert_rate_hz = data[5] + min_vert_offset;
|
|
|
|
base->max_vert_rate_hz = data[6] + max_vert_offset;
|
|
|
|
base->min_horiz_rate_hz = (data[7] + min_horiz_offset) * 1000;
|
|
|
|
base->max_horiz_rate_hz = (data[8] + max_horiz_offset) * 1000;
|
2022-06-06 23:54:06 +02:00
|
|
|
|
2022-08-08 19:34:01 +02:00
|
|
|
if (base->min_vert_rate_hz > base->max_vert_rate_hz) {
|
2022-08-08 09:48:04 +02:00
|
|
|
add_failure(edid, "Display Range Limits: Min vertical rate > max vertical rate.");
|
2022-06-29 12:21:04 +02:00
|
|
|
return false;
|
|
|
|
}
|
2022-08-08 19:34:01 +02:00
|
|
|
if (base->min_horiz_rate_hz > base->max_horiz_rate_hz) {
|
2022-08-08 09:48:04 +02:00
|
|
|
add_failure(edid, "Display Range Limits: Min horizontal freq > max horizontal freq.");
|
2022-06-06 23:54:06 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-08-08 19:34:01 +02:00
|
|
|
base->max_pixel_clock_hz = (int32_t) data[9] * 10 * 1000 * 1000;
|
|
|
|
if (edid->revision == 4 && base->max_pixel_clock_hz == 0) {
|
2022-08-08 09:48:04 +02:00
|
|
|
add_failure(edid, "Display Range Limits: EDID 1.4 block does not set max dotclock.");
|
2022-06-06 23:54:06 +02:00
|
|
|
}
|
|
|
|
|
2022-06-17 12:58:17 +02:00
|
|
|
support_flags = data[10];
|
|
|
|
switch (support_flags) {
|
|
|
|
case 0x00:
|
|
|
|
/* For EDID 1.4 and later, always indicates support for default
|
|
|
|
* GTF. For EDID 1.3 and earlier, a misc features bit indicates
|
|
|
|
* support for default GTF. */
|
|
|
|
if (edid->revision >= 4 || edid->misc_features.default_gtf) {
|
2022-08-08 19:34:01 +02:00
|
|
|
base->type = DI_EDID_DISPLAY_RANGE_LIMITS_DEFAULT_GTF;
|
2022-06-17 12:58:17 +02:00
|
|
|
} else {
|
2022-08-08 19:34:01 +02:00
|
|
|
base->type = DI_EDID_DISPLAY_RANGE_LIMITS_BARE;
|
2022-06-17 12:58:17 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x01:
|
|
|
|
if (edid->revision < 4) {
|
|
|
|
/* Reserved */
|
2022-08-08 09:48:04 +02:00
|
|
|
add_failure(edid, "Display Range Limits: 'Bare Limits' is not allowed for EDID < 1.4.");
|
2022-06-17 12:58:17 +02:00
|
|
|
return false;
|
|
|
|
}
|
2022-08-08 19:34:01 +02:00
|
|
|
base->type = DI_EDID_DISPLAY_RANGE_LIMITS_BARE;
|
2022-06-17 12:58:17 +02:00
|
|
|
break;
|
|
|
|
case 0x02:
|
2022-08-08 19:34:01 +02:00
|
|
|
base->type = DI_EDID_DISPLAY_RANGE_LIMITS_SECONDARY_GTF;
|
2022-06-17 12:58:17 +02:00
|
|
|
break;
|
|
|
|
case 0x04:
|
|
|
|
if (edid->revision < 4) {
|
|
|
|
/* Reserved */
|
2022-08-08 09:48:04 +02:00
|
|
|
add_failure(edid, "Display Range Limits: 'CVT' is not allowed for EDID < 1.4.");
|
2022-06-17 12:58:17 +02:00
|
|
|
return false;
|
|
|
|
}
|
2022-08-08 19:34:01 +02:00
|
|
|
base->type = DI_EDID_DISPLAY_RANGE_LIMITS_CVT;
|
2022-06-17 12:58:17 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* Reserved */
|
|
|
|
if (edid->revision <= 4) {
|
2022-08-08 09:48:04 +02:00
|
|
|
add_failure(edid,
|
|
|
|
"Display Range Limits: Unknown range class (0x%02x).",
|
|
|
|
support_flags);
|
2022-06-17 12:58:17 +02:00
|
|
|
return false;
|
|
|
|
}
|
2022-08-08 19:34:01 +02:00
|
|
|
base->type = DI_EDID_DISPLAY_RANGE_LIMITS_BARE;
|
2022-06-17 12:58:17 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Some types require the display to support continuous frequencies, but
|
|
|
|
* this flag is only set for EDID 1.4 and later */
|
|
|
|
if (edid->revision >= 4 && !edid->misc_features.continuous_freq) {
|
2022-08-08 19:34:01 +02:00
|
|
|
switch (base->type) {
|
2022-06-17 12:58:17 +02:00
|
|
|
case DI_EDID_DISPLAY_RANGE_LIMITS_DEFAULT_GTF:
|
|
|
|
case DI_EDID_DISPLAY_RANGE_LIMITS_SECONDARY_GTF:
|
2022-08-08 09:48:04 +02:00
|
|
|
add_failure(edid, "Display Range Limits: GTF can't be combined with non-continuous frequencies.");
|
2022-06-17 12:58:17 +02:00
|
|
|
return false;
|
|
|
|
case DI_EDID_DISPLAY_RANGE_LIMITS_CVT:
|
2022-08-08 09:48:04 +02:00
|
|
|
add_failure(edid, "Display Range Limits: CVT can't be combined with non-continuous frequencies.");
|
2022-06-17 12:58:17 +02:00
|
|
|
return false;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-08 19:34:01 +02:00
|
|
|
switch (base->type) {
|
2022-08-08 19:20:51 +02:00
|
|
|
case DI_EDID_DISPLAY_RANGE_LIMITS_SECONDARY_GTF:
|
2022-08-08 19:34:01 +02:00
|
|
|
secondary_gtf = &priv->secondary_gtf;
|
|
|
|
|
|
|
|
if (data[11] != 0)
|
|
|
|
add_failure(edid,
|
|
|
|
"Display Range Limits: Byte 11 is 0x%02x instead of 0x00.",
|
|
|
|
data[11]);
|
|
|
|
|
|
|
|
secondary_gtf->start_freq_hz = data[12] * 2 * 1000;
|
|
|
|
secondary_gtf->c = (float) data[13] / 2;
|
|
|
|
secondary_gtf->m = (float) ((data[15] << 8) | data[14]);
|
|
|
|
secondary_gtf->k = (float) data[16];
|
|
|
|
secondary_gtf->j = (float) data[17] / 2;
|
|
|
|
|
|
|
|
base->secondary_gtf = secondary_gtf;
|
|
|
|
break;
|
2022-08-08 19:20:51 +02:00
|
|
|
case DI_EDID_DISPLAY_RANGE_LIMITS_CVT:
|
2022-08-09 10:22:38 +02:00
|
|
|
cvt = &priv->cvt;
|
|
|
|
|
|
|
|
cvt->version = get_bit_range(data[11], 7, 4);
|
|
|
|
cvt->revision = get_bit_range(data[11], 3, 0);
|
|
|
|
|
|
|
|
base->max_pixel_clock_hz -= get_bit_range(data[12], 7, 2) * 250 * 1000;
|
|
|
|
cvt->max_horiz_px = 8 * ((get_bit_range(data[12], 1, 0) << 8) | data[13]);
|
|
|
|
|
|
|
|
cvt->supported_aspect_ratio = data[14];
|
|
|
|
if (get_bit_range(data[14], 2, 0) != 0)
|
|
|
|
add_failure_until(edid, 4,
|
|
|
|
"Display Range Limits: Reserved bits of byte 14 are non-zero.");
|
|
|
|
|
|
|
|
preferred_aspect_ratio = get_bit_range(data[15], 7, 5);
|
|
|
|
switch (preferred_aspect_ratio) {
|
|
|
|
case 0:
|
|
|
|
cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_4_3;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_16_9;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_16_10;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_5_4;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
cvt->preferred_aspect_ratio = DI_EDID_CVT_ASPECT_RATIO_15_9;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* Reserved */
|
|
|
|
add_failure_until(edid, 4,
|
|
|
|
"Display Range Limits: Invalid preferred aspect ratio 0x%02x.",
|
|
|
|
preferred_aspect_ratio);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
cvt->standard_blanking = has_bit(data[15], 3);
|
|
|
|
cvt->reduced_blanking = has_bit(data[15], 4);
|
|
|
|
|
|
|
|
if (get_bit_range(data[15], 2, 0) != 0)
|
|
|
|
add_failure_until(edid, 4,
|
|
|
|
"Display Range Limits: Reserved bits of byte 15 are non-zero.");
|
|
|
|
|
|
|
|
cvt->supported_scaling = data[16];
|
|
|
|
if (get_bit_range(data[16], 3, 0) != 0)
|
|
|
|
add_failure_until(edid, 4,
|
|
|
|
"Display Range Limits: Reserved bits of byte 16 are non-zero.");
|
|
|
|
|
|
|
|
cvt->preferred_vert_refresh_hz = data[17];
|
|
|
|
if (cvt->preferred_vert_refresh_hz == 0) {
|
|
|
|
add_failure_until(edid, 4,
|
|
|
|
"Display Range Limits: Preferred vertical refresh rate must be specified.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
base->cvt = cvt;
|
2022-08-08 19:20:51 +02:00
|
|
|
break;
|
|
|
|
case DI_EDID_DISPLAY_RANGE_LIMITS_BARE:
|
|
|
|
case DI_EDID_DISPLAY_RANGE_LIMITS_DEFAULT_GTF:
|
|
|
|
if (data[11] != 0x0A)
|
|
|
|
add_failure(edid,
|
|
|
|
"Display Range Limits: Byte 11 is 0x%02x instead of 0x0a.",
|
|
|
|
data[11]);
|
|
|
|
for (i = 12; i < EDID_BYTE_DESCRIPTOR_SIZE; i++) {
|
|
|
|
if (data[i] != 0x20) {
|
|
|
|
add_failure(edid,
|
|
|
|
"Display Range Limits: Bytes 12-17 must be 0x20.");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2022-06-06 23:54:06 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-08-15 18:54:22 +02:00
|
|
|
static bool
|
|
|
|
parse_standard_timings_descriptor(struct di_edid *edid,
|
|
|
|
const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE],
|
|
|
|
struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
struct di_edid_standard_timing *t;
|
|
|
|
size_t i;
|
|
|
|
const uint8_t *timing_data;
|
|
|
|
|
|
|
|
for (i = 0; i < EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT; i++) {
|
|
|
|
timing_data = &data[5 + i * EDID_STANDARD_TIMING_SIZE];
|
|
|
|
if (!parse_standard_timing(edid, timing_data, &t))
|
|
|
|
return false;
|
2022-09-06 11:19:48 +02:00
|
|
|
if (t) {
|
2022-09-28 02:44:35 +02:00
|
|
|
assert(desc->standard_timings_len < EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT);
|
2022-08-15 18:54:22 +02:00
|
|
|
desc->standard_timings[desc->standard_timings_len++] = t;
|
2022-09-06 11:19:48 +02:00
|
|
|
}
|
2022-08-15 18:54:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (data[17] != 0x0A)
|
|
|
|
add_failure_until(edid, 4,
|
|
|
|
"Standard Timing Identifications: Last byte must be a line feed.");
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-09-06 10:22:05 +02:00
|
|
|
/**
|
|
|
|
* Mapping table for established timings III.
|
|
|
|
*
|
|
|
|
* Contains one entry per bit, with the value set to the DMT ID.
|
|
|
|
*/
|
|
|
|
static const uint8_t established_timings_iii[] = {
|
|
|
|
/* 0x06 */
|
|
|
|
0x01, /* 640 x 350 @ 85 Hz */
|
|
|
|
0x02, /* 640 x 400 @ 85 Hz */
|
|
|
|
0x03, /* 720 x 400 @ 85 Hz */
|
|
|
|
0x07, /* 640 x 480 @ 85 Hz */
|
|
|
|
0x0e, /* 848 x 480 @ 60 Hz */
|
|
|
|
0x0c, /* 800 x 600 @ 85 Hz */
|
|
|
|
0x13, /* 1024 x 768 @ 85 Hz */
|
|
|
|
0x15, /* 1152 x 864 @ 75 Hz */
|
|
|
|
/* 0x07 */
|
|
|
|
0x16, /* 1280 x 768 @ 60 Hz (RB) */
|
|
|
|
0x17, /* 1280 x 768 @ 60 Hz */
|
|
|
|
0x18, /* 1280 x 768 @ 75 Hz */
|
|
|
|
0x19, /* 1280 x 768 @ 85 Hz */
|
|
|
|
0x20, /* 1280 x 960 @ 60 Hz */
|
|
|
|
0x21, /* 1280 x 960 @ 85 Hz */
|
|
|
|
0x23, /* 1280 x 1024 @ 60 Hz */
|
|
|
|
0x25, /* 1280 x 1024 @ 85 Hz */
|
|
|
|
/* 0x08 */
|
|
|
|
0x27, /* 1360 x 768 @ 60 Hz */
|
|
|
|
0x2e, /* 1440 x 900 @ 60 Hz (RB) */
|
|
|
|
0x2f, /* 1440 x 900 @ 60 Hz */
|
|
|
|
0x30, /* 1440 x 900 @ 75 Hz */
|
|
|
|
0x31, /* 1440 x 900 @ 85 Hz */
|
|
|
|
0x29, /* 1400 x 1050 @ 60 Hz (RB) */
|
|
|
|
0x2a, /* 1400 x 1050 @ 60 Hz */
|
|
|
|
0x2b, /* 1400 x 1050 @ 75 Hz */
|
|
|
|
/* 0x09 */
|
|
|
|
0x2c, /* 1400 x 1050 @ 85 Hz */
|
|
|
|
0x39, /* 1680 x 1050 @ 60 Hz (RB) */
|
|
|
|
0x3a, /* 1680 x 1050 @ 60 Hz */
|
|
|
|
0x3b, /* 1680 x 1050 @ 75 Hz */
|
|
|
|
0x3c, /* 1680 x 1050 @ 85 Hz */
|
|
|
|
0x33, /* 1600 x 1200 @ 60 Hz */
|
|
|
|
0x34, /* 1600 x 1200 @ 65 Hz */
|
|
|
|
0x35, /* 1600 x 1200 @ 70 Hz */
|
|
|
|
/* 0x0a */
|
|
|
|
0x36, /* 1600 x 1200 @ 75 Hz */
|
|
|
|
0x37, /* 1600 x 1200 @ 85 Hz */
|
|
|
|
0x3e, /* 1792 x 1344 @ 60 Hz */
|
|
|
|
0x3f, /* 1792 x 1344 @ 75 Hz */
|
|
|
|
0x41, /* 1856 x 1392 @ 60 Hz */
|
|
|
|
0x42, /* 1856 x 1392 @ 75 Hz */
|
|
|
|
0x44, /* 1920 x 1200 @ 60 Hz (RB) */
|
|
|
|
0x45, /* 1920 x 1200 @ 60 Hz */
|
|
|
|
/* 0x0b */
|
|
|
|
0x46, /* 1920 x 1200 @ 75 Hz */
|
|
|
|
0x47, /* 1920 x 1200 @ 85 Hz */
|
|
|
|
0x49, /* 1920 x 1440 @ 60 Hz */
|
|
|
|
0x4a, /* 1920 x 1440 @ 75 Hz */
|
|
|
|
};
|
|
|
|
static_assert(EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT
|
|
|
|
== sizeof(established_timings_iii) / sizeof(established_timings_iii[0]),
|
|
|
|
"Invalid number of established timings III in table");
|
|
|
|
|
|
|
|
static const struct di_dmt_timing *
|
|
|
|
get_dmt_timing(uint8_t dmt_id)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
const struct di_dmt_timing *t;
|
|
|
|
|
|
|
|
for (i = 0; i < _di_dmt_timings_len; i++) {
|
|
|
|
t = &_di_dmt_timings[i];
|
|
|
|
if (t->dmt_id == dmt_id)
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
parse_established_timings_iii_descriptor(struct di_edid *edid,
|
|
|
|
const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE],
|
|
|
|
struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
size_t i, offset, bit;
|
|
|
|
uint8_t dmt_id;
|
|
|
|
const struct di_dmt_timing *t;
|
|
|
|
bool has_zeroes;
|
|
|
|
|
|
|
|
if (edid->revision < 4)
|
|
|
|
add_failure(edid, "Established timings III: Not allowed for EDID < 1.4.");
|
|
|
|
|
|
|
|
for (i = 0; i < EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT; i++) {
|
|
|
|
dmt_id = established_timings_iii[i];
|
|
|
|
offset = 0x06 + i / 8;
|
|
|
|
bit = 7 - i % 8;
|
|
|
|
assert(offset < EDID_BYTE_DESCRIPTOR_SIZE);
|
|
|
|
if (has_bit(data[offset], bit)) {
|
|
|
|
t = get_dmt_timing(dmt_id);
|
|
|
|
assert(t != NULL);
|
|
|
|
desc->established_timings_iii[desc->established_timings_iii_len++] = t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
has_zeroes = get_bit_range(data[11], 3, 0) == 0;
|
|
|
|
for (i = 12; i < EDID_BYTE_DESCRIPTOR_SIZE; i++) {
|
|
|
|
has_zeroes = has_zeroes && data[i] == 0;
|
|
|
|
}
|
|
|
|
if (!has_zeroes) {
|
|
|
|
add_failure_until(edid, 4,
|
|
|
|
"Established timings III: Reserved bits must be set to zero.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-05 21:00:41 +02:00
|
|
|
static bool
|
|
|
|
parse_color_point_descriptor(struct di_edid *edid,
|
|
|
|
const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE],
|
|
|
|
struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
struct di_edid_color_point *c;
|
|
|
|
|
|
|
|
if (data[5] == 0) {
|
|
|
|
add_failure(edid, "White Point Index Number set to reserved value 0");
|
|
|
|
}
|
|
|
|
|
|
|
|
c = calloc(1, sizeof(*c));
|
|
|
|
if (!c) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
c->index = data[5];
|
|
|
|
c->white_x = decode_chromaticity_coord(data[7], get_bit_range(data[6], 3, 2));
|
|
|
|
c->white_y = decode_chromaticity_coord(data[8], get_bit_range(data[6], 1, 0));
|
|
|
|
|
|
|
|
if (data[9] != 0xFF) {
|
|
|
|
c->gamma = ((float) data[9] + 100) / 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
desc->color_points[desc->color_points_len++] = c;
|
|
|
|
if (data[10] == 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
c = calloc(1, sizeof(*c));
|
|
|
|
if (!c) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
c->index = data[10];
|
|
|
|
c->white_x = decode_chromaticity_coord(data[12], get_bit_range(data[11], 3, 2));
|
|
|
|
c->white_y = decode_chromaticity_coord(data[13], get_bit_range(data[11], 1, 0));
|
|
|
|
|
|
|
|
if (data[14] != 0xFF) {
|
|
|
|
c->gamma = ((float) data[14] + 100) / 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
desc->color_points[desc->color_points_len++] = c;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-09-21 19:45:22 +02:00
|
|
|
static void
|
|
|
|
parse_color_management_data_descriptor(struct di_edid *edid,
|
|
|
|
const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE],
|
|
|
|
struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
desc->dcm_data.version = data[5];
|
|
|
|
|
|
|
|
desc->dcm_data.red_a3 = (uint16_t)(data[6] | (data[7] << 8)) / 100.0f;
|
|
|
|
desc->dcm_data.red_a2 = (uint16_t)(data[8] | (data[9] << 8)) / 100.0f;
|
|
|
|
desc->dcm_data.green_a3 = (uint16_t)(data[10] | (data[11] << 8)) / 100.0f;
|
|
|
|
desc->dcm_data.green_a2 = (uint16_t)(data[12] | (data[13] << 8)) / 100.0f;
|
|
|
|
desc->dcm_data.blue_a3 = (uint16_t)(data[14] | (data[15] << 8)) / 100.0f;
|
|
|
|
desc->dcm_data.blue_a2 = (uint16_t)(data[16] | (data[17] << 8)) / 100.0f;
|
|
|
|
|
|
|
|
if (desc->dcm_data.version != 3) {
|
|
|
|
add_failure_until(edid, 4,
|
|
|
|
"Color Management Data version must be 3");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-28 02:46:42 +02:00
|
|
|
static bool
|
|
|
|
is_cvt_timing_code_preferred_vrate_supported(const struct di_edid_cvt_timing_code *t)
|
|
|
|
{
|
|
|
|
switch (t->preferred_vertical_rate) {
|
|
|
|
case DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_50HZ:
|
|
|
|
return t->supports_50hz_sb;
|
|
|
|
case DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_60HZ:
|
|
|
|
return t->supports_60hz_sb || t->supports_60hz_rb;
|
|
|
|
case DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_75HZ:
|
|
|
|
return t->supports_75hz_sb;
|
|
|
|
case DI_EDID_CVT_TIMING_CODE_PREFERRED_VRATE_85HZ:
|
|
|
|
return t->supports_85hz_sb;
|
|
|
|
}
|
|
|
|
abort(); /* unreachable */
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
parse_cvt_timing_code(struct di_edid *edid,
|
|
|
|
const uint8_t data[static EDID_CVT_TIMING_CODE_SIZE],
|
|
|
|
struct di_edid_cvt_timing_code **out,
|
|
|
|
bool first)
|
|
|
|
{
|
|
|
|
struct di_edid_cvt_timing_code *t;
|
|
|
|
int32_t raw;
|
|
|
|
|
|
|
|
*out = NULL;
|
|
|
|
|
|
|
|
if (!first && data[0] == 0 && data[1] == 0 && data[2] == 0) {
|
|
|
|
/* Unused */
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (data[0] == 0) {
|
|
|
|
add_failure(edid,
|
|
|
|
"CVT byte 0 is 0, which is a reserved value.");
|
|
|
|
}
|
|
|
|
|
|
|
|
t = calloc(1, sizeof(*t));
|
|
|
|
if (!t) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
raw = (int32_t)(data[0] | (get_bit_range(data[1], 7, 4) << 8));
|
|
|
|
t->addressable_lines_per_field = (raw + 1) * 2;
|
|
|
|
t->aspect_ratio = get_bit_range(data[1], 3, 2);
|
|
|
|
|
|
|
|
if (get_bit_range(data[1], 1, 0) != 0) {
|
|
|
|
add_failure(edid,
|
|
|
|
"Reserved bits of CVT byte 1 are non-zero.");
|
|
|
|
}
|
|
|
|
|
|
|
|
t->supports_50hz_sb = has_bit(data[2], 4);
|
|
|
|
t->supports_60hz_sb = has_bit(data[2], 3);
|
|
|
|
t->supports_75hz_sb = has_bit(data[2], 2);
|
|
|
|
t->supports_85hz_sb = has_bit(data[2], 1);
|
|
|
|
t->supports_60hz_rb = has_bit(data[2], 0);
|
|
|
|
|
|
|
|
if (get_bit_range(data[2], 4, 0) == 0) {
|
|
|
|
add_failure(edid,
|
|
|
|
"CVT byte 2 does not support any vertical rates.");
|
|
|
|
}
|
|
|
|
|
|
|
|
t->preferred_vertical_rate = get_bit_range(data[2], 6, 5);
|
|
|
|
|
|
|
|
if (has_bit(data[2], 7) != 0) {
|
|
|
|
add_failure(edid,
|
|
|
|
"Reserved bit of CVT byte 2 is non-zero.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!is_cvt_timing_code_preferred_vrate_supported(t))
|
|
|
|
add_failure(edid, "The preferred CVT Vertical Rate is not supported.");
|
|
|
|
|
|
|
|
*out = t;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
parse_cvt_timing_codes_descriptor(struct di_edid *edid,
|
|
|
|
const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE],
|
|
|
|
struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
struct di_edid_cvt_timing_code *t;
|
|
|
|
size_t i;
|
|
|
|
const uint8_t *timing_data;
|
|
|
|
|
|
|
|
if (data[5] != 1) {
|
|
|
|
add_failure_until(edid, 4, "Invalid version number %u.",
|
|
|
|
data[5]);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT; i++) {
|
|
|
|
timing_data = &data[6 + i * EDID_CVT_TIMING_CODE_SIZE];
|
|
|
|
if (!parse_cvt_timing_code(edid, timing_data, &t, !i))
|
|
|
|
return false;
|
|
|
|
if (t) {
|
|
|
|
assert(desc->cvt_timing_codes_len < EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT);
|
|
|
|
desc->cvt_timing_codes[desc->cvt_timing_codes_len++] = t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-05-24 09:05:14 +02:00
|
|
|
static bool
|
|
|
|
parse_byte_descriptor(struct di_edid *edid,
|
|
|
|
const uint8_t data[static EDID_BYTE_DESCRIPTOR_SIZE])
|
|
|
|
{
|
|
|
|
struct di_edid_display_descriptor *desc;
|
2022-08-17 00:47:08 +02:00
|
|
|
struct di_edid_detailed_timing_def_priv *detailed_timing_def;
|
2022-05-24 09:05:14 +02:00
|
|
|
uint8_t tag;
|
2022-05-24 09:38:40 +02:00
|
|
|
char *newline;
|
2022-05-24 09:05:14 +02:00
|
|
|
|
|
|
|
if (data[0] || data[1]) {
|
|
|
|
if (edid->display_descriptors_len > 0) {
|
|
|
|
/* A detailed timing descriptor is not allowed after a
|
|
|
|
* display descriptor per note 3 of table 3.20. */
|
2022-06-29 12:21:04 +02:00
|
|
|
add_failure(edid, "Invalid detailed timing descriptor ordering.");
|
2022-05-24 09:05:14 +02:00
|
|
|
}
|
|
|
|
|
2022-08-02 18:56:02 +02:00
|
|
|
detailed_timing_def = _di_edid_parse_detailed_timing_def(data);
|
|
|
|
if (!detailed_timing_def) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-09-06 11:19:48 +02:00
|
|
|
assert(edid->detailed_timing_defs_len < EDID_BYTE_DESCRIPTOR_COUNT);
|
2022-08-02 18:56:02 +02:00
|
|
|
edid->detailed_timing_defs[edid->detailed_timing_defs_len++] = detailed_timing_def;
|
|
|
|
return true;
|
2022-05-24 09:05:14 +02:00
|
|
|
}
|
|
|
|
|
2022-08-08 09:38:35 +02:00
|
|
|
if (edid->revision >= 3 && edid->revision <= 4 &&
|
|
|
|
edid->detailed_timing_defs_len == 0) {
|
|
|
|
/* Per section 3.10.1 */
|
|
|
|
add_failure(edid,
|
|
|
|
"The first byte descriptor must contain the preferred timing.");
|
|
|
|
}
|
2022-05-24 09:05:14 +02:00
|
|
|
|
|
|
|
desc = calloc(1, sizeof(*desc));
|
|
|
|
if (!desc) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
tag = data[3];
|
|
|
|
switch (tag) {
|
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_SERIAL:
|
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING:
|
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME:
|
2022-05-24 09:38:40 +02:00
|
|
|
memcpy(desc->str, &data[5], 13);
|
|
|
|
|
|
|
|
/* A newline (if any) indicates the end of the string. */
|
|
|
|
newline = strchr(desc->str, '\n');
|
|
|
|
if (newline) {
|
|
|
|
newline[0] = '\0';
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_RANGE_LIMITS:
|
2022-06-06 23:54:06 +02:00
|
|
|
if (!parse_display_range_limits(edid, data, &desc->range_limits)) {
|
|
|
|
free(desc);
|
2022-06-29 12:21:04 +02:00
|
|
|
return true;
|
2022-06-06 23:54:06 +02:00
|
|
|
}
|
|
|
|
break;
|
2022-05-24 09:05:14 +02:00
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS:
|
2022-08-15 18:54:22 +02:00
|
|
|
if (!parse_standard_timings_descriptor(edid, data, desc)) {
|
|
|
|
free(desc);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
2022-09-06 10:22:05 +02:00
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_ESTABLISHED_TIMINGS_III:
|
|
|
|
parse_established_timings_iii_descriptor(edid, data, desc);
|
|
|
|
break;
|
2022-08-15 18:54:22 +02:00
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT:
|
2022-09-05 21:00:41 +02:00
|
|
|
if (!parse_color_point_descriptor(edid, data, desc)) {
|
|
|
|
free(desc);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
2022-05-24 09:05:14 +02:00
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_DCM_DATA:
|
2022-09-21 19:45:22 +02:00
|
|
|
parse_color_management_data_descriptor(edid, data, desc);
|
|
|
|
break;
|
2022-05-24 09:05:14 +02:00
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES:
|
2022-09-28 02:46:42 +02:00
|
|
|
if (!parse_cvt_timing_codes_descriptor(edid, data, desc)) {
|
|
|
|
free(desc);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
2022-05-24 09:05:14 +02:00
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_DUMMY:
|
|
|
|
break; /* Ignore */
|
|
|
|
default:
|
|
|
|
free(desc);
|
|
|
|
if (tag <= 0x0F) {
|
|
|
|
/* Manufacturer-specific */
|
|
|
|
} else {
|
2022-06-29 12:21:04 +02:00
|
|
|
add_failure_until(edid, 4, "Unknown Type 0x%02hhx.", tag);
|
2022-05-24 09:05:14 +02:00
|
|
|
}
|
2022-06-29 12:21:04 +02:00
|
|
|
return true;
|
2022-05-24 09:05:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
desc->tag = tag;
|
2022-09-06 11:19:48 +02:00
|
|
|
assert(edid->display_descriptors_len < EDID_BYTE_DESCRIPTOR_COUNT);
|
2022-05-24 09:05:14 +02:00
|
|
|
edid->display_descriptors[edid->display_descriptors_len++] = desc;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-05-24 18:09:43 +02:00
|
|
|
static bool
|
|
|
|
parse_ext(struct di_edid *edid, const uint8_t data[static EDID_BLOCK_SIZE])
|
2022-05-03 10:35:25 +02:00
|
|
|
{
|
|
|
|
struct di_edid_ext *ext;
|
2022-05-24 18:09:43 +02:00
|
|
|
uint8_t tag;
|
2022-08-02 12:48:33 +02:00
|
|
|
struct di_logger logger;
|
|
|
|
char section_name[64];
|
2022-05-03 10:35:25 +02:00
|
|
|
|
|
|
|
if (!validate_block_checksum(data)) {
|
|
|
|
errno = EINVAL;
|
2022-05-24 18:09:43 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-06-11 11:38:49 +02:00
|
|
|
ext = calloc(1, sizeof(*ext));
|
|
|
|
if (!ext) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-05-24 18:09:43 +02:00
|
|
|
tag = data[0x00];
|
|
|
|
switch (tag) {
|
|
|
|
case DI_EDID_EXT_CEA:
|
2022-08-02 12:48:33 +02:00
|
|
|
snprintf(section_name, sizeof(section_name),
|
|
|
|
"Block %zu, CTA-861 Extension Block",
|
|
|
|
edid->exts_len + 1);
|
|
|
|
logger = (struct di_logger) {
|
|
|
|
.f = edid->logger->f,
|
|
|
|
.section = section_name,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!_di_edid_cta_parse(&ext->cta, data, EDID_BLOCK_SIZE, &logger)) {
|
2022-06-11 11:38:49 +02:00
|
|
|
free(ext);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
2022-05-24 18:09:43 +02:00
|
|
|
case DI_EDID_EXT_VTB:
|
|
|
|
case DI_EDID_EXT_DI:
|
|
|
|
case DI_EDID_EXT_LS:
|
|
|
|
case DI_EDID_EXT_DPVL:
|
|
|
|
case DI_EDID_EXT_BLOCK_MAP:
|
|
|
|
case DI_EDID_EXT_VENDOR:
|
|
|
|
/* Supported */
|
|
|
|
break;
|
2022-08-31 12:16:36 +02:00
|
|
|
case DI_EDID_EXT_DISPLAYID:
|
|
|
|
snprintf(section_name, sizeof(section_name),
|
|
|
|
"Block %zu, DisplayID Extension Block",
|
|
|
|
edid->exts_len + 1);
|
|
|
|
logger = (struct di_logger) {
|
|
|
|
.f = edid->logger->f,
|
|
|
|
.section = section_name,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!_di_displayid_parse(&ext->displayid, &data[1],
|
|
|
|
EDID_BLOCK_SIZE - 2, &logger)) {
|
|
|
|
free(ext);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
2022-05-24 18:09:43 +02:00
|
|
|
default:
|
|
|
|
/* Unsupported */
|
2022-06-11 11:38:49 +02:00
|
|
|
free(ext);
|
2022-06-29 12:21:04 +02:00
|
|
|
add_failure_until(edid, 4, "Unknown Extension Block.");
|
|
|
|
return true;
|
2022-05-03 10:35:25 +02:00
|
|
|
}
|
|
|
|
|
2022-05-24 18:09:43 +02:00
|
|
|
ext->tag = tag;
|
2022-09-06 11:19:48 +02:00
|
|
|
assert(edid->exts_len < EDID_MAX_BLOCK_COUNT - 1);
|
2022-05-24 18:09:43 +02:00
|
|
|
edid->exts[edid->exts_len++] = ext;
|
|
|
|
return true;
|
2022-05-03 10:35:25 +02:00
|
|
|
}
|
|
|
|
|
2022-03-24 13:23:07 +01:00
|
|
|
struct di_edid *
|
2022-08-02 12:18:19 +02:00
|
|
|
_di_edid_parse(const void *data, size_t size, FILE *failure_msg_file)
|
2022-03-24 13:23:07 +01:00
|
|
|
{
|
|
|
|
struct di_edid *edid;
|
2022-08-02 12:18:19 +02:00
|
|
|
struct di_logger logger;
|
2022-03-24 13:23:07 +01:00
|
|
|
int version, revision;
|
2022-05-03 10:35:25 +02:00
|
|
|
size_t exts_len, i;
|
2022-06-20 16:41:09 +02:00
|
|
|
const uint8_t *standard_timing_data, *byte_desc_data, *ext_data;
|
2022-08-15 18:53:16 +02:00
|
|
|
struct di_edid_standard_timing *standard_timing;
|
2022-03-24 13:23:07 +01:00
|
|
|
|
|
|
|
if (size < EDID_BLOCK_SIZE ||
|
2022-05-13 09:43:08 +02:00
|
|
|
size > EDID_MAX_BLOCK_COUNT * EDID_BLOCK_SIZE ||
|
|
|
|
size % EDID_BLOCK_SIZE != 0) {
|
2022-03-24 13:23:07 +01:00
|
|
|
errno = EINVAL;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (memcmp(data, header, sizeof(header)) != 0) {
|
|
|
|
errno = EINVAL;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
parse_version_revision(data, &version, &revision);
|
|
|
|
if (version != 1) {
|
|
|
|
/* Only EDID version 1 is supported -- as per section 2.1.7
|
|
|
|
* subsequent versions break the structure */
|
|
|
|
errno = ENOTSUP;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!validate_block_checksum(data)) {
|
|
|
|
errno = EINVAL;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-05-25 17:32:25 +02:00
|
|
|
exts_len = size / EDID_BLOCK_SIZE - 1;
|
|
|
|
if (exts_len != parse_ext_count(data)) {
|
2022-08-02 20:09:21 +02:00
|
|
|
errno = EINVAL;
|
2022-05-25 17:32:25 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-03-24 13:23:07 +01:00
|
|
|
edid = calloc(1, sizeof(*edid));
|
|
|
|
if (!edid) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-08-02 12:18:19 +02:00
|
|
|
logger = (struct di_logger) {
|
|
|
|
.f = failure_msg_file,
|
|
|
|
.section = "Block 0, Base EDID",
|
|
|
|
};
|
|
|
|
edid->logger = &logger;
|
|
|
|
|
2022-03-24 13:23:07 +01:00
|
|
|
edid->version = version;
|
|
|
|
edid->revision = revision;
|
|
|
|
|
2022-06-22 09:19:02 +02:00
|
|
|
parse_vendor_product(edid, data);
|
2022-08-02 22:16:14 +02:00
|
|
|
parse_basic_params_features(edid, data);
|
2022-08-02 22:17:18 +02:00
|
|
|
parse_chromaticity_coords(edid, data);
|
2022-06-06 12:03:15 +02:00
|
|
|
|
2022-08-02 17:42:57 +02:00
|
|
|
parse_established_timings_i_ii(edid, data);
|
|
|
|
|
2022-06-20 16:41:09 +02:00
|
|
|
for (i = 0; i < EDID_MAX_STANDARD_TIMING_COUNT; i++) {
|
|
|
|
standard_timing_data = (const uint8_t *) data
|
|
|
|
+ 0x26 + i * EDID_STANDARD_TIMING_SIZE;
|
2022-08-15 18:53:16 +02:00
|
|
|
if (!parse_standard_timing(edid, standard_timing_data,
|
|
|
|
&standard_timing)) {
|
2022-06-20 16:41:09 +02:00
|
|
|
_di_edid_destroy(edid);
|
|
|
|
return NULL;
|
|
|
|
}
|
2022-08-15 18:53:16 +02:00
|
|
|
if (standard_timing) {
|
2022-09-06 11:19:48 +02:00
|
|
|
assert(edid->standard_timings_len < EDID_MAX_STANDARD_TIMING_COUNT);
|
2022-08-15 18:53:16 +02:00
|
|
|
edid->standard_timings[edid->standard_timings_len++] = standard_timing;
|
|
|
|
}
|
2022-06-20 16:41:09 +02:00
|
|
|
}
|
|
|
|
|
2022-05-24 09:05:14 +02:00
|
|
|
for (i = 0; i < EDID_BYTE_DESCRIPTOR_COUNT; i++) {
|
|
|
|
byte_desc_data = (const uint8_t *) data
|
|
|
|
+ 0x36 + i * EDID_BYTE_DESCRIPTOR_SIZE;
|
2022-08-03 09:32:12 +02:00
|
|
|
if (!parse_byte_descriptor(edid, byte_desc_data)) {
|
2022-06-09 14:45:47 +02:00
|
|
|
_di_edid_destroy(edid);
|
2022-05-24 09:05:14 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-03 10:35:25 +02:00
|
|
|
for (i = 0; i < exts_len; i++) {
|
|
|
|
ext_data = (const uint8_t *) data + (i + 1) * EDID_BLOCK_SIZE;
|
2022-06-29 12:21:04 +02:00
|
|
|
if (!parse_ext(edid, ext_data)) {
|
2022-06-09 14:45:47 +02:00
|
|
|
_di_edid_destroy(edid);
|
2022-05-03 10:35:25 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-02 12:18:19 +02:00
|
|
|
edid->logger = NULL;
|
2022-03-24 13:23:07 +01:00
|
|
|
return edid;
|
|
|
|
}
|
|
|
|
|
2022-08-15 18:54:22 +02:00
|
|
|
static void
|
|
|
|
destroy_display_descriptor(struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
switch (desc->tag) {
|
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS:
|
|
|
|
for (i = 0; i < desc->standard_timings_len; i++) {
|
|
|
|
free(desc->standard_timings[i]);
|
|
|
|
}
|
|
|
|
break;
|
2023-04-14 13:00:50 +02:00
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT:
|
|
|
|
for (i = 0; i < desc->color_points_len; i++) {
|
|
|
|
free(desc->color_points[i]);
|
|
|
|
}
|
|
|
|
break;
|
2022-09-28 02:46:42 +02:00
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES:
|
|
|
|
for (i = 0; i < desc->cvt_timing_codes_len; i++) {
|
|
|
|
free(desc->cvt_timing_codes[i]);
|
|
|
|
}
|
|
|
|
break;
|
2022-08-15 18:54:22 +02:00
|
|
|
default:
|
|
|
|
break; /* Nothing to do */
|
|
|
|
}
|
|
|
|
free(desc);
|
|
|
|
}
|
|
|
|
|
2022-03-24 13:23:07 +01:00
|
|
|
void
|
2022-06-09 14:45:47 +02:00
|
|
|
_di_edid_destroy(struct di_edid *edid)
|
2022-03-24 13:23:07 +01:00
|
|
|
{
|
2022-05-03 10:35:25 +02:00
|
|
|
size_t i;
|
2022-06-11 14:06:23 +02:00
|
|
|
struct di_edid_ext *ext;
|
2022-05-03 10:35:25 +02:00
|
|
|
|
2022-06-20 16:41:09 +02:00
|
|
|
for (i = 0; i < edid->standard_timings_len; i++) {
|
|
|
|
free(edid->standard_timings[i]);
|
|
|
|
}
|
|
|
|
|
2022-06-10 18:19:38 +02:00
|
|
|
for (i = 0; i < edid->detailed_timing_defs_len; i++) {
|
|
|
|
free(edid->detailed_timing_defs[i]);
|
|
|
|
}
|
|
|
|
|
2022-05-24 09:05:14 +02:00
|
|
|
for (i = 0; i < edid->display_descriptors_len; i++) {
|
2022-08-15 18:54:22 +02:00
|
|
|
destroy_display_descriptor(edid->display_descriptors[i]);
|
2022-05-24 09:05:14 +02:00
|
|
|
}
|
|
|
|
|
2022-05-03 10:35:25 +02:00
|
|
|
for (i = 0; edid->exts[i] != NULL; i++) {
|
2022-06-11 14:06:23 +02:00
|
|
|
ext = edid->exts[i];
|
|
|
|
switch (ext->tag) {
|
|
|
|
case DI_EDID_EXT_CEA:
|
|
|
|
_di_edid_cta_finish(&ext->cta);
|
|
|
|
break;
|
2022-09-02 12:48:35 +02:00
|
|
|
case DI_EDID_EXT_DISPLAYID:
|
|
|
|
_di_displayid_finish(&ext->displayid);
|
|
|
|
break;
|
2022-06-11 14:06:23 +02:00
|
|
|
default:
|
|
|
|
break; /* Nothing to do */
|
|
|
|
}
|
|
|
|
free(ext);
|
2022-05-03 10:35:25 +02:00
|
|
|
}
|
|
|
|
|
2022-03-24 13:23:07 +01:00
|
|
|
free(edid);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
di_edid_get_version(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return edid->version;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
di_edid_get_revision(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return edid->revision;
|
|
|
|
}
|
2022-03-24 17:54:29 +01:00
|
|
|
|
|
|
|
const struct di_edid_vendor_product *
|
|
|
|
di_edid_get_vendor_product(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return &edid->vendor_product;
|
|
|
|
}
|
2022-05-03 10:35:25 +02:00
|
|
|
|
2022-08-03 11:40:37 +02:00
|
|
|
const struct di_edid_video_input_analog *
|
|
|
|
di_edid_get_video_input_analog(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return edid->is_digital ? NULL : &edid->video_input_analog;
|
|
|
|
}
|
|
|
|
|
2022-05-10 12:20:55 +02:00
|
|
|
const struct di_edid_video_input_digital *
|
|
|
|
di_edid_get_video_input_digital(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return edid->is_digital ? &edid->video_input_digital : NULL;
|
|
|
|
}
|
|
|
|
|
2022-05-10 12:53:52 +02:00
|
|
|
const struct di_edid_screen_size *
|
|
|
|
di_edid_get_screen_size(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return &edid->screen_size;
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:15:26 +02:00
|
|
|
float
|
|
|
|
di_edid_get_basic_gamma(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return edid->gamma;
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:30:54 +02:00
|
|
|
const struct di_edid_dpms *
|
|
|
|
di_edid_get_dpms(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return &edid->dpms;
|
|
|
|
}
|
|
|
|
|
2022-06-17 13:35:24 +02:00
|
|
|
enum di_edid_display_color_type
|
|
|
|
di_edid_get_display_color_type(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return edid->display_color_type;
|
|
|
|
}
|
|
|
|
|
2022-05-13 10:07:13 +02:00
|
|
|
const struct di_edid_color_encoding_formats *
|
|
|
|
di_edid_get_color_encoding_formats(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
/* If color encoding formats are specified, RGB 4:4:4 is always
|
|
|
|
* supported. */
|
|
|
|
return edid->color_encoding_formats.rgb444 ? &edid->color_encoding_formats : NULL;
|
|
|
|
}
|
|
|
|
|
2022-05-13 10:42:59 +02:00
|
|
|
const struct di_edid_misc_features *
|
|
|
|
di_edid_get_misc_features(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return &edid->misc_features;
|
|
|
|
}
|
|
|
|
|
2022-06-06 12:03:15 +02:00
|
|
|
const struct di_edid_chromaticity_coords *
|
|
|
|
di_edid_get_chromaticity_coords(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return &edid->chromaticity_coords;
|
|
|
|
}
|
|
|
|
|
2022-08-02 17:42:57 +02:00
|
|
|
const struct di_edid_established_timings_i_ii *
|
|
|
|
di_edid_get_established_timings_i_ii(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return &edid->established_timings_i_ii;
|
|
|
|
}
|
|
|
|
|
2022-06-20 16:41:09 +02:00
|
|
|
int32_t
|
|
|
|
di_edid_standard_timing_get_vert_video(const struct di_edid_standard_timing *t)
|
|
|
|
{
|
|
|
|
switch (t->aspect_ratio) {
|
|
|
|
case DI_EDID_STANDARD_TIMING_16_10:
|
|
|
|
return t->horiz_video * 10 / 16;
|
|
|
|
case DI_EDID_STANDARD_TIMING_4_3:
|
|
|
|
return t->horiz_video * 3 / 4;
|
|
|
|
case DI_EDID_STANDARD_TIMING_5_4:
|
|
|
|
return t->horiz_video * 4 / 5;
|
|
|
|
case DI_EDID_STANDARD_TIMING_16_9:
|
|
|
|
return t->horiz_video * 9 / 16;
|
|
|
|
}
|
|
|
|
abort(); /* unreachable */
|
|
|
|
}
|
|
|
|
|
2022-08-15 20:27:30 +02:00
|
|
|
const struct di_dmt_timing *
|
|
|
|
di_edid_standard_timing_get_dmt(const struct di_edid_standard_timing *t)
|
2022-06-22 13:06:37 +02:00
|
|
|
{
|
|
|
|
int32_t vert_video;
|
|
|
|
size_t i;
|
|
|
|
const struct di_dmt_timing *dmt;
|
|
|
|
|
|
|
|
vert_video = di_edid_standard_timing_get_vert_video(t);
|
|
|
|
|
|
|
|
for (i = 0; i < _di_dmt_timings_len; i++) {
|
|
|
|
dmt = &_di_dmt_timings[i];
|
|
|
|
|
|
|
|
if (dmt->horiz_video == t->horiz_video
|
|
|
|
&& dmt->vert_video == vert_video
|
|
|
|
&& dmt->refresh_rate_hz == (float)t->refresh_rate_hz
|
|
|
|
&& dmt->edid_std_id != 0) {
|
2022-08-15 20:27:30 +02:00
|
|
|
return dmt;
|
2022-06-22 13:06:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-06-20 16:41:09 +02:00
|
|
|
const struct di_edid_standard_timing *const *
|
|
|
|
di_edid_get_standard_timings(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return (const struct di_edid_standard_timing *const *) &edid->standard_timings;
|
|
|
|
}
|
|
|
|
|
2022-06-10 18:19:38 +02:00
|
|
|
const struct di_edid_detailed_timing_def *const *
|
|
|
|
di_edid_get_detailed_timing_defs(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return (const struct di_edid_detailed_timing_def *const *) &edid->detailed_timing_defs;
|
|
|
|
}
|
|
|
|
|
2022-05-24 09:05:14 +02:00
|
|
|
const struct di_edid_display_descriptor *const *
|
|
|
|
di_edid_get_display_descriptors(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return (const struct di_edid_display_descriptor *const *) &edid->display_descriptors;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum di_edid_display_descriptor_tag
|
|
|
|
di_edid_display_descriptor_get_tag(const struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
return desc->tag;
|
|
|
|
}
|
|
|
|
|
2022-05-24 09:38:40 +02:00
|
|
|
const char *
|
|
|
|
di_edid_display_descriptor_get_string(const struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
switch (desc->tag) {
|
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_SERIAL:
|
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING:
|
|
|
|
case DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME:
|
|
|
|
return desc->str;
|
|
|
|
default:
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-06 23:54:06 +02:00
|
|
|
const struct di_edid_display_range_limits *
|
|
|
|
di_edid_display_descriptor_get_range_limits(const struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_RANGE_LIMITS) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2022-08-08 19:34:01 +02:00
|
|
|
return &desc->range_limits.base;
|
2022-06-06 23:54:06 +02:00
|
|
|
}
|
|
|
|
|
2022-08-15 18:54:22 +02:00
|
|
|
const struct di_edid_standard_timing *const *
|
|
|
|
di_edid_display_descriptor_get_standard_timings(const struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_STD_TIMING_IDS) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return (const struct di_edid_standard_timing *const *) desc->standard_timings;
|
|
|
|
}
|
|
|
|
|
2022-09-05 21:00:41 +02:00
|
|
|
const struct di_edid_color_point *const *
|
|
|
|
di_edid_display_descriptor_get_color_points(const struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_COLOR_POINT) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return (const struct di_edid_color_point *const *) desc->color_points;
|
|
|
|
}
|
|
|
|
|
2022-09-06 10:22:05 +02:00
|
|
|
const struct di_dmt_timing *const *
|
|
|
|
di_edid_display_descriptor_get_established_timings_iii(const struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_ESTABLISHED_TIMINGS_III) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return desc->established_timings_iii;
|
|
|
|
}
|
|
|
|
|
2022-09-21 19:45:22 +02:00
|
|
|
const struct di_edid_color_management_data *
|
|
|
|
di_edid_display_descriptor_get_color_management_data(const struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_DCM_DATA) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return &desc->dcm_data;
|
|
|
|
}
|
|
|
|
|
2022-09-28 02:46:42 +02:00
|
|
|
const struct di_edid_cvt_timing_code *const *
|
|
|
|
di_edid_display_descriptor_get_cvt_timing_codes(const struct di_edid_display_descriptor *desc)
|
|
|
|
{
|
|
|
|
if (desc->tag != DI_EDID_DISPLAY_DESCRIPTOR_CVT_TIMING_CODES) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return (const struct di_edid_cvt_timing_code *const *) desc->cvt_timing_codes;
|
|
|
|
}
|
|
|
|
|
2022-05-03 10:35:25 +02:00
|
|
|
const struct di_edid_ext *const *
|
|
|
|
di_edid_get_extensions(const struct di_edid *edid)
|
|
|
|
{
|
|
|
|
return (const struct di_edid_ext *const *) edid->exts;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum di_edid_ext_tag
|
|
|
|
di_edid_ext_get_tag(const struct di_edid_ext *ext)
|
|
|
|
{
|
|
|
|
return ext->tag;
|
|
|
|
}
|
2022-06-11 11:38:49 +02:00
|
|
|
|
|
|
|
const struct di_edid_cta *
|
|
|
|
di_edid_ext_get_cta(const struct di_edid_ext *ext)
|
|
|
|
{
|
|
|
|
if (ext->tag != DI_EDID_EXT_CEA) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return &ext->cta;
|
|
|
|
}
|
2022-08-31 12:16:36 +02:00
|
|
|
|
|
|
|
const struct di_displayid *
|
|
|
|
di_edid_ext_get_displayid(const struct di_edid_ext *ext)
|
|
|
|
{
|
|
|
|
if (ext->tag != DI_EDID_EXT_DISPLAYID) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return &ext->displayid;
|
|
|
|
}
|