summaryrefslogtreecommitdiffstats
path: root/src/common/impl/edidHelper.c
blob: 1e3310e2a42a7bf85cac83a9d39f90aacf9dc389 (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
125
126
127
128
129
130
131
132
#include "common/edidHelper.h"

void ffEdidGetPhysicalResolution(const uint8_t edid[128], uint32_t* width, uint32_t* height) {
    const int dtd = 54;
    *width = (((uint32_t) edid[dtd + 4] >> 4) << 8) | edid[dtd + 2];
    *height = (((uint32_t) edid[dtd + 7] >> 4) << 8) | edid[dtd + 5];
}

void ffEdidGetPreferredResolutionAndRefreshRate(const uint8_t edid[128], uint32_t* width, uint32_t* height, double* refreshRate) {
    for (uint32_t i = 0x36; i < 0x7E; i += 0x12) {    // read through descriptor blocks...
        if (edid[i] != 0x00 && edid[i + 1] != 0x00) { // a dtd
            uint32_t hactive = edid[i + 2] + (uint32_t) ((edid[i + 4] & 0xf0) << 4);
            uint32_t hblank = edid[i + 3] + (uint32_t) ((edid[i + 4] & 0x0f) << 8);
            uint32_t vactive = edid[i + 5] + (uint32_t) ((edid[i + 7] & 0xf0) << 4);
            uint32_t vblank = edid[i + 6] + (uint32_t) ((edid[i + 7] & 0x0f) << 8);
            uint32_t pixclk = ((uint32_t) edid[i + 1] << 8) | (edid[i]);
            *width = hactive;
            *height = vactive;
            *refreshRate = (double) pixclk * 10000 / (double) (hactive + hblank) / (double) (vactive + vblank);
            return;
        }
    }
}

void ffEdidGetVendorAndModel(const uint8_t edid[128], FFstrbuf* result) {
    // https://github.com/jinksong/read_edid/blob/master/parse-edid/parse-edid.c
    ffStrbufAppendF(result, "%c%c%c%04X", (char) (((uint32_t) edid[8] >> 2 & 0x1f) + 'A' - 1), (char) (((((uint32_t) edid[8] & 0x3) << 3) | (((uint32_t) edid[9] & 0xe0) >> 5)) + 'A' - 1), (char) (((uint32_t) edid[9] & 0x1f) + 'A' - 1), (uint32_t) (edid[10] + (uint32_t) (edid[11] << 8)));
}

bool ffEdidGetName(const uint8_t edid[128], FFstrbuf* name) {
    // https://github.com/jinksong/read_edid/blob/master/parse-edid/parse-edid.c
    for (uint32_t i = 0x36; i < 0x7E; i += 0x12) { // read through descriptor blocks...
        if (edid[i] == 0x00) {                     // not a timing descriptor
            if (edid[i + 3] == 0xfc) {             // Model Name tag
                for (uint32_t j = 0; j < 13; j++) {
                    if (edid[i + 5 + j] == 0x0a) {
                        ffStrbufAppendNS(name, j, (const char*) &edid[i + 5]);
                        return true;
                    }
                }
            }
        }
    }

    // use manufacturer + model number as monitor name
    ffEdidGetVendorAndModel(edid, name);
    return false;
}

void ffEdidGetPhysicalSize(const uint8_t edid[128], uint32_t* width, uint32_t* height) {
    // Detailed Timing Descriptors
    uint32_t dw = (((uint32_t) edid[68] & 0xF0) << 4) + edid[66];
    uint32_t dh = (((uint32_t) edid[68] & 0x0F) << 8) + edid[67];

    // Basic Display Parameters
    uint32_t bw = edid[21] * 10;
    uint32_t bh = edid[22] * 10;

    // Some monitors report invalid data in DTD. See #1406
    if (abs((int) dw - (int) bw) < 10 && abs((int) dh - (int) bh) < 10) {
        *width = dw;
        *height = dh;
    } else {
        *width = bw;
        *height = bh;
    }
}

void ffEdidGetSerialAndManufactureDate(const uint8_t edid[128], uint32_t* serial, uint16_t* year, uint16_t* week) {
    if (edid[17] > 0 && edid[17] < 0xFF) {
        *year = (uint16_t) (edid[17] + 1990);
        *week = (uint16_t) edid[16];
        if (*week == 0xFF) {
            *week = 0;
        }
    } else {
        *year = *week = 0;
    }

    *serial = *(uint32_t*) &edid[12];
}

bool ffEdidGetHdrCompatible(const uint8_t* edid, uint32_t length) {
    if (length <= 128) {
        return false;
    }
    for (const uint8_t* cta = &edid[128]; cta < &edid[length]; cta += 128) {
        // https://en.wikipedia.org/wiki/Extended_Display_Identification_Data#CTA_EDID_Timing_Extension_Block
        if (cta[0] != 0x02 /* CTA EDID */) {
            continue;
        }
        if (cta[1] < 0x03 /* Version 3 */) {
            continue;
        }
        const uint8_t offset = cta[2];
        if (offset <= 4) {
            continue;
        }
        for (uint8_t i = 4; i < offset;) {
            uint8_t blkLen = cta[i] & 0x1f;
            if (blkLen > 0) {
                uint8_t blkTag = (cta[i] & 0xe0) >> 5;
                if (blkTag == 0x07 /* Extended Block Type Tag */) {
                    uint8_t extendedTag = cta[i + 1];
                    if (extendedTag == 6 /* HDR SMDB */ || extendedTag == 7 /* HDR DMDB */) {
                        return true;
                    }
                }
            }
            i += (uint8_t) (blkLen + 1);
        }
    }
    return false;
}

bool ffEdidIsValid(const uint8_t edid[128], uint32_t length) {
    if (length < 128 || length % 128 != 0) {
        return false;
    }

    static const uint8_t edidHeader[] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 };
    if (memcmp(edid, edidHeader, sizeof(edidHeader)) != 0) {
        return false;
    }

    uint8_t sum = 0;
    for (uint32_t i = 0; i < 128; i++) {
        sum += edid[i];
    }

    return sum == 0;
}