/* * Copyright 2009 Red Hat, Inc. * * 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 * on the rights to use, copy, modify, merge, publish, distribute, sub * license, and/or sell copies of the Software, and to permit persons to whom * them 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 MERCHANTIBILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS 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: * Adam Jackson */ #include "xorg-config.h" #include "xf86.h" #include "xf86str.h" #include "edid.h" #include "xf86DDC.h" typedef void (*did_proc)(int scrnIndex, unsigned char *data, void *closure); #define DID_PRODUCT_ID 0x00 #define DID_DISPLAY_PARAMETERS 0x01 #define DID_COLOR_INFO 0x02 #define DID_TIMING_1_DETAILED 0x03 #define DID_TIMING_2_DETAILED 0x04 #define DID_TIMING_3_SHORT 0x05 #define DID_TIMING_4_DMT 0x06 #define DID_TIMING_VESA 0x07 #define DID_TIMING_CEA 0x08 #define DID_TIMING_RANGE_LIMITS 0x09 #define DID_PRODUCT_SERIAL 0x0A #define DID_ASCII_STRING 0x0B #define DID_DISPLAY_DEVICE 0x0C #define DID_POWER_SEQUENCING 0x0D #define DID_TRANSFER_INFO 0x0E #define DID_DISPLAY_INTERFACE 0x0F #define DID_STEREO 0x10 #define DID_VENDOR 0x7F #define extract_le16(x, i) ((x[i+1] << 8) + (x[i])) #define extract_le24(x, i) ((x[i+2] << 16) + (x[i+1] << 8) + (x[i])) static DisplayModePtr modeCalloc(void) { return calloc(1, sizeof(DisplayModeRec)); } /* * How awesome is it to have two detailed timing formats, neither of which * are compatible with the format in EDID? So awesome. */ static void didDetailedTiming1(int i, unsigned char *x, MonPtr mon) { DisplayModePtr m = modeCalloc(); if (!m) return; m->Clock = extract_le24(x, 0); m->HDisplay = extract_le16(x, 4); m->HSyncStart = m->HDisplay + (extract_le16(x, 8) & 0x7f); m->HSyncEnd = m->HSyncStart + extract_le16(x, 10); m->HTotal = m->HDisplay + extract_le16(x, 6); m->Flags |= (x[9] & 0x80) ? V_PHSYNC : V_NHSYNC; m->VDisplay = extract_le16(x, 12); m->VSyncStart = m->VDisplay + (extract_le16(x, 16) & 0x7f); m->VSyncEnd = m->VSyncStart + extract_le16(x, 18); m->VTotal = m->VDisplay + extract_le16(x, 14); m->Flags |= (x[17] & 0x80) ? V_PVSYNC : V_NVSYNC; m->type = M_T_DRIVER; if (x[3] & 0x80) m->type |= M_T_PREFERRED; /* XXX double check handling of this */ if (x[3] & 0x10) m->Flags |= V_INTERLACE; mon->Modes = xf86ModesAdd(mon->Modes, m); } /* XXX no sync bits. what to do? */ static void didDetailedTiming2(int i, unsigned char *x, MonPtr mon) { DisplayModePtr mode = modeCalloc(); if (!mode) return; mode->Clock = extract_le24(x, 0); /* horiz sizes are in character cells, not pixels, hence * 8 */ mode->HDisplay = ((extract_le16(x, 4) & 0x01ff) + 1) * 8; mode->HSyncStart = mode->HDisplay + (((x[6] & 0xf0) >> 4) + 1) * 8; mode->HSyncEnd = mode->HSyncStart + ((x[6] & 0x0f) + 1) * 8; mode->HTotal = mode->HDisplay + ((x[5] >> 1) + 1) * 8; mode->VDisplay = extract_le16(x, 7) & 0x07ff; mode->VSyncStart = mode->VDisplay + (x[10] >> 4) + 1; mode->VSyncEnd = mode->VSyncStart + (x[10] & 0x0f) + 1; mode->VTotal = mode->VDisplay + x[9]; mode->status = M_T_DRIVER; if (x[3] & 0x80) mode->status |= M_T_PREFERRED; /* XXX double check handling of this */ if (x[3] & 0x10) mode->Flags |= V_INTERLACE; mon->Modes = xf86ModesAdd(mon->Modes, mode); } static void didShortTiming(int i, unsigned char *x, MonPtr mon) { DisplayModePtr m; int w, h, r; w = (x[1] + 1) * 8; switch (x[0] & 0x0f) { case 0: h = w; break; case 1: h = (w * 4) / 5; break; case 2: h = (w * 3) / 4; break; case 3: h = (w * 9) / 15; break; case 4: h = (w * 9) / 16; break; case 5: h = (w * 10) / 16; break; default: return; } r = (x[2] & 0x7f) + 1; m = xf86CVTMode(w, h, r, !!(x[0] & 0x10), !!(x[2] & 0x80)); m->type = M_T_DRIVER; if (x[0] & 0x80) m->type |= M_T_PREFERRED; mon->Modes = xf86ModesAdd(mon->Modes, m); } static void didDMTTiming(int i, unsigned char *x, void *closure) { MonPtr mon = closure; mon->Modes = xf86ModesAdd(mon->Modes, xf86DuplicateMode(DMTModes + *x)); } #define RB 1 #define INT 2 static const struct did_dmt { short w, h, r, f; } did_dmt[] = { /* byte 3 */ { 640, 350, 85, 0 }, { 640, 400, 85, 0 }, { 720, 400, 85, 0 }, { 640, 480, 60, 0 }, { 640, 480, 72, 0 }, { 640, 480, 75, 0 }, { 640, 480, 85, 0 }, { 800, 600, 56, 0 }, /* byte 4 */ { 800, 600, 60, 0 }, { 800, 600, 72, 0 }, { 800, 600, 75, 0 }, { 800, 600, 85, 0 }, { 800, 600, 120, RB }, { 848, 480, 60, 0 }, { 1024, 768, 43, INT }, { 1024, 768, 60, 0 }, /* byte 5 */ { 1024, 768, 70, 0 }, { 1024, 768, 75, 0 }, { 1024, 768, 85, 0 }, { 1024, 768, 120, RB }, { 1152, 864, 75, 0 }, { 1280, 768, 60, RB }, { 1280, 768, 60, 0 }, { 1280, 768, 75, 0 }, /* byte 6 */ { 1280, 768, 85, 0 }, { 1280, 768, 120, RB }, { 1280, 800, 60, RB }, { 1280, 800, 60, 0 }, { 1280, 800, 75, 0 }, { 1280, 800, 85, 0 }, { 1280, 800, 120, RB }, { 1280, 960, 60, 0 }, /* byte 7 */ { 1280, 960, 85, 0 }, { 1280, 960, 120, RB }, { 1280, 1024, 60, 0 }, { 1280, 1024, 75, 0 }, { 1280, 1024, 85, 0 }, { 1280, 1024, 120, RB }, { 1360, 768, 60, 0 }, { 1360, 768, 120, RB }, /* byte 8 */ { 1400, 1050, 60, RB }, { 1400, 1050, 60, 0 }, { 1400, 1050, 75, 0 }, { 1400, 1050, 85, 0 }, { 1400, 1050, 120, RB }, { 1440, 900, 60, RB }, { 1440, 900, 60, 0 }, { 1440, 900, 75, 0 }, /* byte 9 */ { 1440, 900, 85, 0 }, { 1440, 900, 120, RB }, { 1600, 1200, 60, 0 }, { 1600, 1200, 65, 0 }, { 1600, 1200, 70, 0 }, { 1600, 1200, 75, 0 }, { 1600, 1200, 85, 0 }, { 1600, 1200, 120, RB }, /* byte a */ { 1680, 1050, 60, RB }, { 1680, 1050, 60, 0 }, { 1680, 1050, 75, 0 }, { 1680, 1050, 85, 0 }, { 1680, 1050, 120, RB }, { 1792, 1344, 60, 0 }, { 1792, 1344, 75, 0 }, { 1792, 1344, 120, RB }, /* byte b */ { 1856, 1392, 60, 0 }, { 1856, 1392, 75, 0 }, { 1856, 1392, 120, RB }, { 1920, 1200, 60, RB }, { 1920, 1200, 60, 0 }, { 1920, 1200, 75, 0 }, { 1920, 1200, 85, 0 }, { 1920, 1200, 120, RB }, /* byte c */ { 1920, 1440, 60, 0 }, { 1920, 1440, 75, 0 }, { 1920, 1440, 120, RB }, { 2560, 1600, 60, RB }, { 2560, 1600, 60, 0 }, { 2560, 1600, 75, 0 }, { 2560, 1600, 85, 0 }, { 2560, 1600, 120, RB }, }; static void didVesaTiming(int scrn, unsigned char *x, MonPtr mon) { int i, j; x += 3; for (i = 0; i < 10; i++) for (j = 0; j < 8; j++) if (x[i] & (1 << j)) { const struct did_dmt *d = &(did_dmt[i * 8 + j]); if (d->f == INT) continue; mon->Modes = xf86ModesAdd(mon->Modes, FindDMTMode(d->w, d->h, d->r, d->f == RB)); } } static void handleDisplayIDBlock(int scrnIndex, unsigned char *x, void *closure) { MonPtr mon = closure; switch (x[0]) { case DID_DISPLAY_PARAMETERS: /* w/h are in decimillimeters */ mon->widthmm = (extract_le16(x, 3) + 5) / 10; mon->heightmm = (extract_le16(x, 5) + 5) / 10; /* XXX pixel count, feature flags, gamma, aspect, color depth */ break; case DID_TIMING_RANGE_LIMITS: { int n; mon->maxPixClock = max(mon->maxPixClock, extract_le24(x, 6) * 10); n = mon->nHsync++; if (n < MAX_HSYNC) { mon->hsync[n].lo = x[9]; mon->hsync[n].hi = x[10]; } else { n = MAX_HSYNC; } n = mon->nVrefresh++; if (n < MAX_VREFRESH) { mon->vrefresh[n].lo = x[13]; mon->vrefresh[n].hi = x[14]; } else { n = MAX_VREFRESH; } break; } case DID_TIMING_1_DETAILED: { int i; for (i = 0; i < x[2]; i += 20) didDetailedTiming1(scrnIndex, x + i + 3, mon); break; } case DID_TIMING_2_DETAILED: { int i; for (i = 0; i < x[2]; i += 11) didDetailedTiming2(scrnIndex, x + i + 3, mon); break; } case DID_TIMING_3_SHORT: { int i; for (i = 0; i < x[2]; i += 3) didShortTiming(scrnIndex, x + i + 3, mon); break; } case DID_TIMING_4_DMT: { int i; for (i = 0; i < x[2]; i++) didDMTTiming(scrnIndex, x + i + 3, mon); break; } case DID_TIMING_VESA: didVesaTiming(scrnIndex, x, mon); break; /* XXX pixel format, ar, orientation, subpixel, dot pitch, bit depth */ case DID_DISPLAY_DEVICE: /* XXX interface, links, color encoding, ss, drm */ case DID_DISPLAY_INTERFACE: /* XXX stereo */ case DID_STEREO: /* nothing interesting in these */ case DID_COLOR_INFO: case DID_PRODUCT_SERIAL: case DID_ASCII_STRING: case DID_POWER_SEQUENCING: case DID_TRANSFER_INFO: case DID_VENDOR: break; /* warn about anything else */ default: xf86DrvMsg(scrnIndex, X_WARNING, "Unknown DisplayID block type %hx\n", x[0]); break; } } static void forEachDisplayIDBlock(int scrnIndex, unsigned char *did, did_proc proc, void *closure) { int num_extensions = did[3]; int section_size = did[1]; unsigned char *block; do { if ((did[0] & 0xf0) != 0x10) /* not 1.x, abort */ return; /* XXX also, checksum */ block = did + 4; while (section_size > 0) { int block_size = (block[2] + 2); proc(scrnIndex, block, closure); section_size -= block_size; block += block_size; } did += (did[1] + 5); } while (num_extensions--); } /* * Fill out MonPtr with xf86MonPtr information. */ void xf86DisplayIDMonitorSet(int scrnIndex, MonPtr mon, xf86MonPtr DDC) { if (!mon || !DDC) return; mon->DDC = DDC; forEachDisplayIDBlock(scrnIndex, DDC->rawData, handleDisplayIDBlock, mon); }