#include "minitru-int.h" static const uint8_t header[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; static uint32_t edid_valid_checksum(uint8_t *x) { unsigned char sum = 0; int i; for (i = 0; i < 128; i++) sum += x[i]; return !sum; } static int edid_version(uint8_t *block) { return block[0x12]; } static int edid_revision(uint8_t *block) { return block[0x13]; } static uint32_t edid_probe(struct mt_monitor *mon) { uint8_t *block = mon->block; int i, blocks; if (mon->len < 128 || mon->len % 128) return 0; blocks = block[0x7e] + 1; if (blocks * 128 != mon->len) return 0; if (memcmp(block, header, 8) == 0) return 0; if (edid_version(block) != 1) return 0; for (i = 0; i < mon->len / 128; i++) if (!edid_valid_checksum(block + (i * 128))) return 0; return 1; } /* XXX can probably make this indexes into the DMT list */ static const struct mt_mode established_timings[17] = { { 0, 0, 40000, 800, 840, 968, 1056, 0, 600, 601, 605, 628, 0, MT_FLAG_PHSYNC | MT_FLAG_PVSYNC }, /* 800x600@60Hz */ { 0, 0, 36000, 800, 824, 896, 1024, 0, 600, 601, 603, 625, 0, MT_FLAG_PHSYNC | MT_FLAG_PVSYNC }, /* 800x600@56Hz */ { 0, 0, 31500, 640, 656, 720, 840, 0, 480, 481, 484, 500, 0, MT_FLAG_NHSYNC | MT_FLAG_NVSYNC }, /* 640x480@75Hz */ { 0, 0, 31500, 640, 664, 704, 832, 0, 480, 489, 492, 520, 0, MT_FLAG_NHSYNC | MT_FLAG_NVSYNC }, /* 640x480@72Hz */ { 0, 0, 30240, 640, 704, 768, 864, 0, 480, 483, 486, 525, 0, MT_FLAG_NHSYNC | MT_FLAG_NVSYNC }, /* 640x480@67Hz */ { 0, 0, 25175, 640, 656, 752, 800, 0, 480, 490, 492, 525, 0, MT_FLAG_NHSYNC | MT_FLAG_NVSYNC }, /* 640x480@60Hz */ { 0, 0, 35500, 720, 738, 846, 900, 0, 400, 421, 423, 449, 0, MT_FLAG_NHSYNC | MT_FLAG_NVSYNC }, /* 720x400@88Hz */ { 0, 0, 28320, 720, 738, 846, 900, 0, 400, 412, 414, 449, 0, MT_FLAG_NHSYNC | MT_FLAG_PVSYNC }, /* 720x400@70Hz */ { 0, 0, 135000, 1280, 1296, 1440, 1688, 0, 1024, 1025, 1028, 1066, 0, MT_FLAG_PHSYNC | MT_FLAG_PVSYNC }, /* 1280x1024@75Hz */ { 0, 0, 78750, 1024, 1040, 1136, 1312, 0, 768, 769, 772, 800, 0, MT_FLAG_PHSYNC | MT_FLAG_PVSYNC }, /* 1024x768@75Hz */ { 0, 0, 75000, 1024, 1048, 1184, 1328, 0, 768, 771, 777, 806, 0, MT_FLAG_NHSYNC | MT_FLAG_NVSYNC }, /* 1024x768@70Hz */ { 0, 0, 65000, 1024, 1048, 1184, 1344, 0, 768, 771, 777, 806, 0, MT_FLAG_NHSYNC | MT_FLAG_NVSYNC }, /* 1024x768@60Hz */ { 0, 0, 44900, 1024, 1032, 1208, 1264, 0, 768, 768, 772, 817, 0, MT_FLAG_PHSYNC | MT_FLAG_PVSYNC | MT_FLAG_INTERLACED },/*1024x768@43Hz*/ { 0, 0, 57284, 832, 864, 928, 1152, 0, 624, 625, 628, 667, 0, MT_FLAG_NHSYNC | MT_FLAG_NVSYNC }, /* 832x624@75Hz */ { 0, 0, 49500, 800, 816, 896, 1056, 0, 600, 601, 604, 625, 0, MT_FLAG_PHSYNC | MT_FLAG_PVSYNC }, /* 800x600@75Hz */ { 0, 0, 50000, 800, 856, 976, 1040, 0, 600, 637, 643, 666, 0, MT_FLAG_PHSYNC | MT_FLAG_PVSYNC }, /* 800x600@72Hz */ { 0, 0, 108000, 1152, 1216, 1344, 1600, 0, 864, 865, 868, 900, 0, MT_FLAG_PHSYNC | MT_FLAG_PVSYNC }, /* 1152x864@75Hz */ }; static struct mt_mode * edid_established_modes(struct mt_monitor *mon, struct mt_mode *list) { uint8_t i, *block = mon->block; for (i = 0; i < 17; i++) if (block[0x23 + i / 8] & (1 << (7 - i % 8))) list = mt_modes_add(list, mt_mode_copy(&(established_timings[i]))); return list; } /* XXX might also be in extension blocks */ static int edid_cvt_supported(uint8_t *block) { return block[10] == 0x04; } static int edid_monitor_supports_reduced_blanking(uint8_t *block) { /* XXX wrong for 1.4 */ return block[0x14] && 0x80; } #define LEVEL_DMT 0 #define LEVEL_GTF 1 #define LEVEL_CVT 2 static int standard_timing_level(uint8_t *block) { int revision = edid_revision(block); if (revision >= 2) { if (revision >= 4 && edid_cvt_supported(block)) return LEVEL_CVT; return LEVEL_GTF; } return LEVEL_DMT; } static uint32_t edid_standard_get_vsize(uint8_t *block, uint32_t hsize, uint32_t aspect) { switch (aspect) { case 0: if (edid_revision(block) < 3) return hsize; return hsize * 10 / 16; default: /* can't happen */ case 1: return hsize * 3 / 4; case 2: return hsize * 4 / 5; case 3: return hsize * 9 / 16; } } static int bad_standard_code(uint8_t b1, uint8_t b2) { return (b1 == 0x00 && b2 == 0x00) || (b1 == 0x01 && b2 == 0x01) || (b1 == 0x20 && b2 == 0x20); } static struct mt_mode * edid_standard_mode(uint8_t *block, uint8_t *x) { struct mt_mode *mode; uint32_t hsize, vsize, aspect, refresh; uint32_t rb; if (bad_standard_code(x[0], x[1])) return NULL; hsize = (x[0] + 31) * 8; aspect = (x[1] >> 6) & 0x3; refresh = 60 + (x[1] & 0x3f); vsize = edid_standard_get_vsize(block, hsize, aspect); rb = edid_monitor_supports_reduced_blanking(block); if (hsize == 1360 && vsize == 765 && refresh == 60) { mode = mt_cvt_mode(1366, 768, 60, 0); mode->hdisplay = 1366; mode->vsyncstart--; mode->vsyncend--; return mode; } mode = find_dmt_mode(hsize, vsize, refresh, rb); if (!mode) { int timing_level = standard_timing_level(block); if (timing_level == LEVEL_CVT) /* XXX maybe RB? */ return mt_cvt_mode(hsize, vsize, refresh, 0); if (timing_level == LEVEL_GTF) return mt_gtf_mode(hsize, vsize, refresh, 0); } return NULL; } static struct mt_mode * edid_standard_modes(struct mt_monitor *mon, struct mt_mode *list) { uint8_t i, *block = mon->block; for (i = 0; i < 8; i++) list = mt_modes_add(list, edid_standard_mode(block, block + (0x26 + i * 2))); return list; } /* XXX should be indexes into DMT list */ static const struct { short w; short h; short r; short rb; } est3[] = { /* byte 6 */ { 640, 350, 85, 0 }, { 640, 400, 85, 0 }, { 720, 400, 85, 0 }, { 640, 480, 85, 0 }, { 848, 480, 60, 0 }, { 800, 600, 85, 0 }, { 1024, 768, 85, 0 }, { 1152, 864, 75, 0 }, /* byte 7 */ { 1280, 768, 60, 1 }, { 1280, 768, 60, 0 }, { 1280, 768, 75, 0 }, { 1280, 768, 85, 0 }, { 1280, 960, 60, 0 }, { 1280, 960, 85, 0 }, { 1280, 1024, 60, 0 }, { 1280, 1024, 85, 0 }, /* byte 8 */ { 1360, 768, 60, 0 }, { 1440, 900, 60, 1 }, { 1440, 900, 60, 0 }, { 1440, 900, 75, 0 }, { 1440, 900, 85, 0 }, { 1400, 1050, 60, 1 }, { 1400, 1050, 60, 0 }, { 1400, 1050, 75, 0 }, /* byte 9 */ { 1400, 1050, 85, 0 }, { 1680, 1050, 60, 1 }, { 1680, 1050, 60, 0 }, { 1680, 1050, 75, 0 }, { 1680, 1050, 85, 0 }, { 1600, 1200, 60, 0 }, { 1600, 1200, 65, 0 }, { 1600, 1200, 70, 0 }, /* byte 10 */ { 1600, 1200, 75, 0 }, { 1600, 1200, 85, 0 }, { 1792, 1344, 60, 0 }, { 1792, 1344, 85, 0 }, { 1856, 1392, 60, 0 }, { 1856, 1392, 75, 0 }, { 1920, 1200, 60, 1 }, { 1920, 1200, 60, 0 }, /* byte 11 */ { 1920, 1200, 75, 0 }, { 1920, 1200, 85, 0 }, { 1920, 1440, 60, 0 }, { 1920, 1440, 75, 0 }, }; static struct mt_mode * edid_estiii_modes(uint8_t *x) { struct mt_mode *ret = NULL; int i, j, m; for (i = 0; i < 6; i++) { for (j = 7; j > 0; j--) { if (x[i] & (1 << j)) { m = (i * 8) + (7 - j); ret = mt_modes_add(ret, find_dmt_mode(est3[m].w, est3[m].h, est3[m].r, est3[m].rb)); } } } return ret; } static uint32_t edid_cvt_next_refresh(uint32_t *refreshes, uint32_t *flag) { if (*refreshes & 0x10) { *refreshes &= 0x10; return 50; } if (*refreshes & 0x08) { *refreshes &= 0x08; return 60; } if (*refreshes & 0x04) { *refreshes &= 0x04; return 75; } if (*refreshes & 0x02) { *refreshes &= 0x02; return 85; } if (*refreshes &= 0x01) { *refreshes &= 0x01; *flag = MT_FLAG_REDUCED; return 60; } *refreshes = 0; return 0; } static struct mt_mode * edid_cvt_mode(uint8_t *x) { struct mt_mode *ret = NULL; const uint8_t empty[3] = { 0, 0, 0 }; uint32_t width, height, refreshes; if (!memcmp(x, empty, 3)) return NULL; height = x[0]; height |= (x[1] & 0xf0) << 4; height++; height *= 2; switch (x[1] & 0x0c) { case 0x00: width = (height * 4) / 3; break; case 0x04: width = (height * 16) / 9; break; case 0x08: width = (height * 16) / 10; break; case 0x0c: width = (height * 15) / 9; break; } refreshes = x[2] & 0x1f; while (refreshes) { uint32_t flag = 0; uint32_t refresh = edid_cvt_next_refresh(&refreshes, &flag); ret = mt_modes_add(ret, mt_cvt_mode(width, height, refresh, flag)); } return ret; } static struct mt_mode * edid_cvt_modes(uint8_t *x) { struct mt_mode *ret = NULL; int i; for (i = 0; i < 4; i++) ret = mt_modes_add(ret, edid_cvt_mode(x + 6 + (i * 3))); return ret; } static struct mt_mode * edid_detailed_standard_modes(uint8_t *block, uint8_t *x) { struct mt_mode *ret = NULL; uint8_t i; for (i = 0; i < 6; i++) ret = mt_modes_add(ret, edid_standard_mode(block, x + i*2)); return ret; } static struct mt_mode * edid_do_detailed_mode(uint8_t *x) { uint32_t ha, hbl, hso, hspw, hborder, va, vbl, vso, vspw, vborder; struct mt_mode *m = mt_mode_alloc(); ha = (x[2] + ((x[4] & 0xF0) << 4)); hbl = (x[3] + ((x[4] & 0x0F) << 8)); hso = (x[8] + ((x[11] & 0xC0) << 2)); hspw = (x[9] + ((x[11] & 0x30) << 4)); hborder = x[15]; va = (x[5] + ((x[7] & 0xF0) << 4)); vbl = (x[6] + ((x[7] & 0x0F) << 8)); vso = ((x[10] >> 4) + ((x[11] & 0x0C) << 2)); vspw = ((x[10] & 0x0F) + ((x[11] & 0x03) << 4)); vborder = x[16]; m->clock = (x[0] + (x[1] << 8)) * 10; m->hdisplay = ha; m->hsyncstart = ha + hso; m->hsyncend = ha + hso + hspw; m->htotal = ha + hbl; /* hborder */ m->vdisplay = va; m->vsyncstart = va + vso; m->vsyncend = va + vso + vspw; m->vtotal = va + vbl; /* vborder */ if (x[17] & 0x04) m->flags |= MT_FLAG_PVSYNC; else m->flags |= MT_FLAG_NVSYNC; if (x[17] & 0x02) m->flags |= MT_FLAG_PHSYNC; else m->flags |= MT_FLAG_NHSYNC; if (x[17] & 0x80) m->flags |= MT_FLAG_INTERLACED; if ((m->htotal - m->hdisplay) == 160 && (m->hsyncend - m->hdisplay) == 80 && (m->hsyncend - m->hsyncstart) == 32 && (m->vsyncstart - m->vdisplay) == 3) m->flags |= MT_FLAG_REDUCED; return m; } static struct mt_mode * edid_do_detailed_block(uint8_t *block, uint8_t *x) { if (x[0] == 0 && x[1] == 0) { switch (x[3]) { case 0xF7: /* EST III */ return edid_estiii_modes(x); case 0xF8: /* CVT */ return edid_cvt_modes(x); case 0xFA: /* standard */ return edid_detailed_standard_modes(block, x + 6); default: return NULL; } } return edid_do_detailed_mode(x); } static struct mt_mode * edid_detailed_block_modes(struct mt_monitor *mon, struct mt_mode *list) { uint8_t *block = mon->block; int i; for (i = 0; i < 4; i++) list = mt_modes_add(list, edid_do_detailed_block(block, block + 0x36 + (i*18))); return list; } static struct mt_mode * edid_extension_block_modes(struct mt_monitor *mon, struct mt_mode *list) { return list; } static struct mt_mode * edid_modes(struct mt_monitor *mon) { struct mt_mode *mode = NULL; mode = edid_established_modes(mon, mode); mode = edid_standard_modes(mon, mode); mode = edid_detailed_block_modes(mon, mode); mode = edid_extension_block_modes(mon, mode); return mode; } hidden struct mt_backend _mt_edid_backend = { edid_probe, edid_modes, };