aboutsummaryrefslogtreecommitdiffstats
path: root/libgamma_internal_parse_edid.c
blob: 9c8a84fa95e52f0b51bdc9c797fa2561aa64bd44 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/* See LICENSE file for copyright and license details. */
#include "common.h"


/*
 * EDID structure revision 1.1:
 *   http://en.wikipedia.org/w/index.php?title=Extended_display_identification_data&oldid=46295569
 * 
 * EDID structure revision 1.3:
 *   https://en.wikipedia.org/w/index.php?title=Extended_Display_Identification_Data&oldid=805561402#EDID_1.3_data_format
 * 
 * EDID structure revision 1.4:
 *   http://en.wikipedia.org/wiki/Extended_display_identification_data
 */


/**
 * Parse the EDID of a monitor
 * 
 * @param   this    Instance of a data structure to fill with the information about the EDID;
 *                  it must contain the EDID and its length
 * @param   fields  OR:ed identifiers for the information about the EDID that should be parsed;
 *                  fields that do not have to do with EDID are ignored
 * @return          Non-zero on error
 */
int
libgamma_internal_parse_edid(struct libgamma_crtc_information *restrict this, unsigned long long fields)
{
#define M(value)\
	(this->edid[index++] != (value))

	uint16_t red_x, green_x, blue_x, white_x;
	uint16_t red_y, green_y, blue_y, white_y;
	int error = 0, checksum = 0;
	size_t i, index = 0;

	/* If the length of the EDID is less than 128 bytes, we know that it is
	 * not of EDID structure revision 1.0–1.4, and thus we do not support it;
	 * however, extensions allows it to be longer tha 128 bytes */
	if (this->edid_length < 128)
		error = LIBGAMMA_EDID_LENGTH_UNSUPPORTED;
	/* Check that the magic number of that of the EDID structure */
	else if (M(0x00) || M(0xFF) || M(0xFF) || M(0xFF) || M(0xFF) || M(0xFF) || M(0xFF) || M(0x00))
		error = LIBGAMMA_EDID_WRONG_MAGIC_NUMBER;
	/* Check that EDID structure revision 1.x is used. We only know that we
	 * support revisions 1.1, 1.3, and 1.4, and since they are all comptible,
	 * we assume that all 1.x are compatible. */
	else if (this->edid[18] != 1)
		error = LIBGAMMA_EDID_REVISION_UNSUPPORTED;

	/* If we have encountered an error, report it for the fields that require
	 * the EDID to be parsed. Note that it is not stored for the EDID field
	 * itself because it is not considered an error just because we do not
	 * support the used version. */
	this->width_mm_edid_error = this->height_mm_edid_error = this->gamma_error = error;
	this->chroma_error = this->white_point_error = error;

	/* Set errors and exit if we have error, especially important if this->edid_length < 128 */
	if (error)
		goto out;

	/* Retrieve the size of the viewport. This is done even if it is not
	 * requested because it is not worth it to branch. */
	this->width_mm_edid  = (size_t)this->edid[21] * 10;
	this->height_mm_edid = (size_t)this->edid[22] * 10;

	/* Retrieve the monitor's gamma characteristics */
	if (fields & LIBGAMMA_CRTC_INFO_GAMMA) {
		if (this->edid[23] == 0xFF) {
			/* If the gamma charactistics is FFh (3,55) it should be interpreted as not specified. */
			this->gamma_error = LIBGAMMA_GAMMA_NOT_SPECIFIED;
		} else {
			this->gamma_blue = (float)((int)this->edid[23] + 100) / 100.f;
			this->gamma_red = this->gamma_green = this->gamma_blue;
		}
	}

	/* Retrieve the monitor's subpixel chromas */
	if (fields & (LIBGAMMA_CRTC_INFO_CHROMA | LIBGAMMA_CRTC_INFO_WHITE_POINT)) {
		red_x   = (uint16_t)((((uint16_t)this->edid[25] >> 6) & 2) | ((uint16_t)this->edid[27] << 2));
		red_y   = (uint16_t)((((uint16_t)this->edid[25] >> 4) & 2) | ((uint16_t)this->edid[28] << 2));
		green_x = (uint16_t)((((uint16_t)this->edid[25] >> 2) & 2) | ((uint16_t)this->edid[29] << 2));
		green_y = (uint16_t)((((uint16_t)this->edid[25] >> 0) & 2) | ((uint16_t)this->edid[30] << 2));
		blue_x  = (uint16_t)((((uint16_t)this->edid[26] >> 6) & 2) | ((uint16_t)this->edid[31] << 2));
		blue_y  = (uint16_t)((((uint16_t)this->edid[26] >> 4) & 2) | ((uint16_t)this->edid[32] << 2));
		white_x = (uint16_t)((((uint16_t)this->edid[26] >> 2) & 2) | ((uint16_t)this->edid[33] << 2));
		white_y = (uint16_t)((((uint16_t)this->edid[26] >> 6) & 2) | ((uint16_t)this->edid[34] << 2));
		/* Even though the maximum value as encoded is 1023, the values should be divided by 1024 */
		this->red_chroma_x   = (float)red_x   / 1024.f;
		this->red_chroma_y   = (float)red_y   / 1024.f;
		this->green_chroma_x = (float)green_x / 1024.f;
		this->green_chroma_y = (float)green_y / 1024.f;
		this->blue_chroma_x  = (float)blue_x  / 1024.f;
		this->blue_chroma_y  = (float)blue_y  / 1024.f;
		this->white_point_x  = (float)white_x / 1024.f;
		this->white_point_y  = (float)white_y / 1024.f;
	}

	/* Calculate and test the checksum. It is not considered
	 * an error that the gamma characteristics is left
	 * unspecified in the EDID. */
	if (!error)
		for (i = 0; i < 128; i++)
			checksum += (int)this->edid[i];
	/* The checksum should be zero */
	if (checksum & 255) {
		/* Store the error in all fields that require the EDID to be parsed,
		 * as well as the EDID field itself */
		error = LIBGAMMA_EDID_CHECKSUM_ERROR;
	out:
		this->edid_error = this->width_mm_edid_error = this->height_mm_edid_error = error;
		/* If the gamma characteristics is not specified, that is kept,
		 * and the checksum error is augmented. */
		this->gamma_error = this->gamma_error == LIBGAMMA_GAMMA_NOT_SPECIFIED
				? LIBGAMMA_GAMMA_NOT_SPECIFIED_AND_EDID_CHECKSUM_ERROR : error;
	}

	/* Return whether or not we encountered an error or if
	 * the gamma characteristics was requested but is not
	 * specified in the monitor's EDID */
	return error | this->gamma_error;

#undef M
}