From 52f9028c84baea81230dc673b756552e8e90aecd Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Thu, 5 Apr 2007 11:21:06 +1000 Subject: Initial import of modesetting for intel driver in DRM --- linux-core/drm_edid.c | 515 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 linux-core/drm_edid.c (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c new file mode 100644 index 00000000..3c123751 --- /dev/null +++ b/linux-core/drm_edid.c @@ -0,0 +1,515 @@ +#include +#include +#include "drmP.h" +#include "intel_drv.h" + +/* + * DDC/EDID probing rippped off from FB layer + */ + +#include "edid.h" +#define DDC_ADDR 0x50 + +#ifdef BIG_ENDIAN +#error "EDID structure is little endian, need big endian versions" +#endif + +struct est_timings { + u8 t1; + u8 t2; + u8 mfg_rsvd; +} __attribute__((packed)); + +struct std_timing { + u8 hsize; /* need to multiply by 8 then add 248 */ + u8 vfreq:6; /* need to add 60 */ + u8 aspect_ratio:2; /* 00=16:10, 01=4:3, 10=5:4, 11=16:9 */ +} __attribute__((packed)); + +/* If detailed data is pixel timing */ +struct detailed_pixel_timing { + u8 hactive_lo; + u8 hblank_lo; + u8 hblank_hi:4; + u8 hactive_hi:4; + u8 vactive_lo; + u8 vblank_lo; + u8 vblank_hi:4; + u8 vactive_hi:4; + u8 hsync_offset_lo; + u8 hsync_pulse_width_lo; + u8 vsync_pulse_width_lo:4; + u8 vsync_offset_lo:4; + u8 hsync_pulse_width_hi:2; + u8 hsync_offset_hi:2; + u8 vsync_pulse_width_hi:2; + u8 vsync_offset_hi:2; + u8 width_mm_lo; + u8 height_mm_lo; + u8 height_mm_hi:4; + u8 width_mm_hi:4; + u8 hborder; + u8 vborder; + u8 unknown0:1; + u8 vsync_positive:1; + u8 hsync_positive:1; + u8 separate_sync:2; + u8 stereo:1; + u8 unknown6:1; + u8 interlaced:1; +} __attribute__((packed)); + +/* If it's not pixel timing, it'll be one of the below */ +struct detailed_data_string { + u8 str[13]; +} __attribute__((packed)); + +struct detailed_data_monitor_range { + u8 min_vfreq; + u8 max_vfreq; + u8 min_hfreq_khz; + u8 max_hfreq_khz; + u8 pixel_clock_mhz; /* need to multiply by 10 */ + u16 sec_gtf_toggle; /* A000=use above, 20=use below */ + u8 hfreq_start_khz; /* need to multiply by 2 */ + u8 c; /* need to divide by 2 */ + u16 m; + u8 k; + u8 j; /* need to divide by 2 */ +} __attribute__((packed)); + +struct detailed_data_wpindex { + u8 white_y_lo:2; + u8 white_x_lo:2; + u8 pad:4; + u8 white_x_hi; + u8 white_y_hi; + u8 gamma; /* need to divide by 100 then add 1 */ +} __attribute__((packed)); + +struct detailed_data_color_point { + u8 windex1; + u8 wpindex1[3]; + u8 windex2; + u8 wpindex2[3]; +} __attribute__((packed)); + +struct detailed_non_pixel { + u8 pad1; + u8 type; /* ff=serial, fe=string, fd=monitor range, fc=monitor name + fb=color point data, fa=standard timing data, + f9=undefined, f8=mfg. reserved */ + u8 pad2; + union { + struct detailed_data_string str; + struct detailed_data_monitor_range range; + struct detailed_data_wpindex color; + struct std_timing timings[5]; + } data; +} __attribute__((packed)); + +#define EDID_DETAIL_STD_MODES 0xfa +#define EDID_DETAIL_CPDATA 0xfb +#define EDID_DETAIL_NAME 0xfc +#define EDID_DETAIL_RANGE 0xfd +#define EDID_DETAIL_STRING 0xfe +#define EDID_DETAIL_SERIAL 0xff + +struct detailed_timing { + u16 pixel_clock; /* need to multiply by 10 KHz */ + union { + struct detailed_pixel_timing pixel_data; + struct detailed_non_pixel other_data; + } data; +} __attribute__((packed)); + +struct edid { + u8 header[8]; + /* Vendor & product info */ + u16 mfg_id; + u16 prod_code; + u32 serial; + u8 mfg_week; + u8 mfg_year; + /* EDID version */ + u8 version; + u8 revision; + /* Display info: */ + /* input definition */ + u8 serration_vsync:1; + u8 sync_on_green:1; + u8 composite_sync:1; + u8 separate_syncs:1; + u8 blank_to_black:1; + u8 video_level:2; + u8 digital:1; /* bits below must be zero if set */ + u8 width_cm; + u8 height_cm; + u8 gamma; + /* feature support */ + u8 default_gtf:1; + u8 preferred_timing:1; + u8 standard_color:1; + u8 display_type:2; /* 00=mono, 01=rgb, 10=non-rgb, 11=unknown */ + u8 pm_active_off:1; + u8 pm_suspend:1; + u8 pm_standby:1; + /* Color characteristics */ + u8 red_green_lo; + u8 black_white_lo; + u8 red_x; + u8 red_y; + u8 green_x; + u8 green_y; + u8 blue_x; + u8 blue_y; + u8 white_x; + u8 white_y; + /* Est. timings and mfg rsvd timings*/ + struct est_timings established_timings; + /* Standard timings 1-8*/ + struct std_timing standard_timings[8]; + /* Detailing timings 1-4 */ + struct detailed_timing detailed_timings[4]; + /* Number of 128 byte ext. blocks */ + u8 extensions; + /* Checksum */ + u8 checksum; +} __attribute__((packed)); + +static u8 edid_header[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; + +static bool edid_valid(struct edid *edid) +{ + int i; + u8 csum = 0; + u8 *raw_edid = (u8 *)edid; + + if (memcmp(edid->header, edid_header, sizeof(edid_header))) + goto bad; + if (edid->version != 1) + goto bad; + if (edid->revision <= 0 || edid->revision > 3) + goto bad; + + for (i = 0; i < EDID_LENGTH; i++) + csum += raw_edid[i]; + if (csum) + goto bad; + + return 1; + +bad: + return 0; +} + +/** + * drm_mode_std - convert standard mode info (width, height, refresh) into mode + * @t: standard timing params + * + * Take the standard timing params (in this case width, aspect, and refresh) + * and convert them into a real mode using CVT. + * + * Punts for now. + */ +struct drm_display_mode *drm_mode_std(struct std_timing *t) +{ +// struct fb_videomode mode; + +// fb_find_mode_cvt(&mode, 0, 0); + /* JJJ: convert to drm_display_mode */ + struct drm_display_mode *mode; + int hsize = t->hsize * 8 + 248, vsize; + + mode = kzalloc(sizeof(struct drm_display_mode), GFP_KERNEL); + if (!mode) + return NULL; + + if (t->aspect_ratio == 0) + vsize = (hsize * 10) / 16; + else if (t->aspect_ratio == 1) + vsize = (hsize * 3) / 4; + else if (t->aspect_ratio == 2) + vsize = (hsize * 4) / 5; + else + vsize = (hsize * 9) / 16; + + snprintf(mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d", hsize, vsize); + + return mode; +} + +struct drm_display_mode *drm_mode_detailed(struct detailed_timing *timing, + bool preferred) +{ + struct drm_display_mode *mode; + struct detailed_pixel_timing *pt = &timing->data.pixel_data; + + if (pt->stereo) { + printk(KERN_ERR "stereo mode not supported\n"); + return NULL; + } + if (!pt->separate_sync) { + printk(KERN_ERR "integrated sync not supported\n"); + return NULL; + } + + mode = kzalloc(sizeof(struct drm_display_mode), GFP_KERNEL); + if (!mode) + return NULL; + + mode->type = DRM_MODE_TYPE_DRIVER; + mode->type |= preferred ? DRM_MODE_TYPE_PREFERRED : 0; + mode->clock = timing->pixel_clock / 100; + + mode->hdisplay = (pt->hactive_hi << 8) | pt->hactive_lo; + mode->hsync_start = mode->hdisplay + ((pt->hsync_offset_hi << 8) | + pt->hsync_offset_lo); + mode->hsync_end = mode->hsync_start + + ((pt->hsync_pulse_width_hi << 8) | + pt->hsync_pulse_width_lo); + mode->htotal = mode->hdisplay + ((pt->hblank_hi << 8) | pt->hblank_lo); + + mode->vdisplay = (pt->vactive_hi << 8) | pt->vactive_lo; + mode->vsync_start = mode->vdisplay + ((pt->vsync_offset_hi << 8) | + pt->vsync_offset_lo); + mode->vsync_end = mode->vsync_start + + ((pt->vsync_pulse_width_hi << 8) | + pt->vsync_pulse_width_lo); + mode->vtotal = mode->vdisplay + ((pt->vblank_hi << 8) | pt->vblank_lo); + + snprintf(mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d", mode->hdisplay, + mode->vdisplay); + + if (pt->interlaced) + mode->flags |= V_INTERLACE; + + mode->flags |= pt->hsync_positive ? V_PHSYNC : V_NHSYNC; + mode->flags |= pt->vsync_positive ? V_PVSYNC : V_NVSYNC; + + return mode; +} + +static struct drm_display_mode established_modes[] = { + { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 40000, 800, 840, + 968, 1056, 0, 600, 601, 605, 628, 0, + V_PHSYNC | V_PVSYNC) }, /* 800x600@60Hz */ + { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 36000, 800, 824, + 896, 1024, 0, 600, 601, 603, 625, 0, + V_PHSYNC | V_PVSYNC) }, /* 800x600@56Hz */ + { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 656, + 720, 840, 0, 480, 481, 484, 500, 0, + V_NHSYNC | V_NVSYNC) }, /* 640x480@75Hz */ + { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 664, + 704, 832, 0, 480, 489, 491, 520, 0, + V_NHSYNC | V_NVSYNC) }, /* 640x480@72Hz */ + { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 30240, 640, 704, + 768, 864, 0, 480, 483, 486, 525, 0, + V_NHSYNC | V_NVSYNC) }, /* 640x480@67Hz */ + { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25200, 640, 656, + 752, 800, 0, 480, 490, 492, 525, 0, + V_NHSYNC | V_NVSYNC) }, /* 640x480@60Hz */ + { DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 35500, 720, 738, + 846, 900, 0, 400, 421, 423, 449, 0, + V_NHSYNC | V_NVSYNC) }, /* 720x400@88Hz */ + { DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 28320, 720, 738, + 846, 900, 0, 400, 412, 414, 449, 0, + V_NHSYNC | V_PVSYNC) }, /* 720x400@70Hz */ + { DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 135000, 1280, 1296, + 1440, 1688, 0, 1024, 1025, 1028, 1066, 0, + V_PHSYNC | V_PVSYNC) }, /* 1280x1024@75Hz */ + { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 78800, 1024, 1040, + 1136, 1312, 0, 768, 769, 772, 800, 0, + V_PHSYNC | V_PVSYNC) }, /* 1024x768@75Hz */ + { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 75000, 1024, 1048, + 1184, 1328, 0, 768, 771, 777, 806, 0, + V_NHSYNC | V_NVSYNC) }, /* 1024x768@70Hz */ + { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 65000, 1024, 1048, + 1184, 1344, 0, 768, 771, 777, 806, 0, + V_NHSYNC | V_NVSYNC) }, /* 1024x768@60Hz */ + { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER,44900, 1024, 1032, + 1208, 1264, 0, 768, 768, 776, 817, 0, + V_PHSYNC | V_PVSYNC | V_INTERLACE) }, /* 1024x768@43Hz */ + { DRM_MODE("832x624", DRM_MODE_TYPE_DRIVER, 57284, 832, 864, + 928, 1152, 0, 624, 625, 628, 667, 0, + V_NHSYNC | V_NVSYNC) }, /* 832x624@75Hz */ + { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 49500, 800, 816, + 896, 1056, 0, 600, 601, 604, 625, 0, + V_PHSYNC | V_PVSYNC) }, /* 800x600@75Hz */ + { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 50000, 800, 856, + 976, 1040, 0, 600, 637, 643, 666, 0, + V_PHSYNC | V_PVSYNC) }, /* 800x600@72Hz */ + { DRM_MODE("1152x864", DRM_MODE_TYPE_DRIVER, 108000, 1152, 1216, + 1344, 1600, 0, 864, 865, 868, 900, 0, + V_PHSYNC | V_PVSYNC) }, /* 1152x864@75Hz */ +}; + +#define EDID_EST_TIMINGS 16 +#define EDID_STD_TIMINGS 8 +#define EDID_DETAILED_TIMINGS 4 + +/** + * add_established_modes - get est. modes from EDID and add them + * @edid: EDID block to scan + * + * Each EDID block contains a bitmap of the supported "established modes" list + * (defined above). Tease them out and add them to the global modes list. + */ +static int add_established_modes(struct drm_output *output, struct edid *edid) +{ + unsigned long est_bits = edid->established_timings.t1 | + (edid->established_timings.t2 << 8) | + ((edid->established_timings.mfg_rsvd & 0x80) << 9); + int i, modes = 0; + + for (i = 0; i <= EDID_EST_TIMINGS; i++) + if (est_bits & (1<standard_timings[i]; + + /* If std timings bytes are 1, 1 it's empty */ + if (t->hsize == 1 && (t->aspect_ratio | t->vfreq) == 1) + continue; + + drm_mode_probed_add(output, + drm_mode_std(&edid->standard_timings[i])); + modes++; + } + + return modes; +} + +/** + * add_detailed_modes - get detailed mode info from EDID data + * @edid: EDID block to scan + * + * Some of the detailed timing sections may contain mode information. Grab + * it and add it to the list. + */ +static int add_detailed_info(struct drm_output *output, struct edid *edid) +{ + int i, j, modes = 0; + bool preferred = 0; + + for (i = 0; i < EDID_DETAILED_TIMINGS; i++) { + struct detailed_timing *timing = &edid->detailed_timings[i]; + struct detailed_non_pixel *data = &timing->data.other_data; + + /* EDID up to and including 1.2 may put monitor info here */ + if (edid->version == 1 && edid->revision < 3) + continue; + + /* Detailed mode timing */ + if (timing->pixel_clock) { + if (i == 0 && edid->preferred_timing) + preferred = 1; + drm_mode_probed_add(output, + drm_mode_detailed(timing, preferred)); + modes++; + continue; + } + + /* Other timing or info */ + switch (data->type) { + case EDID_DETAIL_SERIAL: + break; + case EDID_DETAIL_STRING: + break; + case EDID_DETAIL_RANGE: + break; + case EDID_DETAIL_NAME: + break; + case EDID_DETAIL_CPDATA: + break; + case EDID_DETAIL_STD_MODES: + /* Five modes per detailed section */ + for (j = 0; j < 5; i++) { + struct std_timing *std; + + std = &data->data.timings[j]; + drm_mode_probed_add(output, drm_mode_std(std)); + modes++; + } + break; + default: + break; + } + } + + return modes; +} + +/** + * drm_add_edid_modes - add modes from EDID data, if available + * @output: output we're probing + * @adapter: i2c adapter to use for DDC + * + * Poke the given output's i2c channel to grab EDID data if possible. If we + * get any, add the specified modes to the output's mode list. + * + * Return number of modes added or 0 if we couldn't find any. + */ +int drm_add_edid_modes(struct drm_output *output, struct i2c_adapter *adapter) +{ + struct edid *edid; + u8 *raw_edid; + int i, est_modes, std_modes, det_modes; + + edid = (struct edid *)fb_ddc_read(adapter); + + if (!edid) { + dev_warn(&output->dev->pdev->dev, "no EDID data\n"); + goto out_err; + } + + if (!edid_valid(edid)) { + dev_warn(&output->dev->pdev->dev, "EDID invalid, ignoring.\n"); + goto out_err; + } + + est_modes = add_established_modes(output, edid); + std_modes = add_standard_modes(output, edid); + det_modes = add_detailed_info(output, edid); + printk(KERN_ERR "est modes: %d, std_modes: %d, det_modes: %d\n", + est_modes, std_modes, det_modes); + + raw_edid = (u8 *)edid; + printk(KERN_ERR "EDID:\n" KERN_ERR); + for (i = 0; i < EDID_LENGTH; i++) { + if (i != 0 && ((i % 16) == 0)) + printk("\n" KERN_ERR); + printk("%02x", raw_edid[i] & 0xff); + } + printk("\n"); + + printk(KERN_ERR "EDID info:\n"); + printk(KERN_ERR " mfg_id: 0x%04x\n", edid->mfg_id); + printk(KERN_ERR " digital? %s\n", edid->digital ? "Yes" : "No"); + printk(KERN_ERR " extensions: %d\n", edid->extensions); + + return est_modes + std_modes + det_modes; + +out_err: + kfree(edid); + return 0; +} +EXPORT_SYMBOL(drm_add_edid_modes); -- cgit v1.2.3 From 5bffbd6e275efffbb649c20c528a11412ccf99cd Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Thu, 5 Apr 2007 13:34:50 +1000 Subject: initial userspace interface to get modes --- linux-core/drm_edid.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index 3c123751..bf1ea94c 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -212,7 +212,8 @@ bad: * * Punts for now. */ -struct drm_display_mode *drm_mode_std(struct std_timing *t) +struct drm_display_mode *drm_mode_std(struct drm_device *dev, + struct std_timing *t) { // struct fb_videomode mode; @@ -221,7 +222,7 @@ struct drm_display_mode *drm_mode_std(struct std_timing *t) struct drm_display_mode *mode; int hsize = t->hsize * 8 + 248, vsize; - mode = kzalloc(sizeof(struct drm_display_mode), GFP_KERNEL); + mode = drm_crtc_mode_create(dev); if (!mode) return NULL; @@ -239,7 +240,8 @@ struct drm_display_mode *drm_mode_std(struct std_timing *t) return mode; } -struct drm_display_mode *drm_mode_detailed(struct detailed_timing *timing, +struct drm_display_mode *drm_mode_detailed(drm_device_t *dev, + struct detailed_timing *timing, bool preferred) { struct drm_display_mode *mode; @@ -254,7 +256,7 @@ struct drm_display_mode *drm_mode_detailed(struct detailed_timing *timing, return NULL; } - mode = kzalloc(sizeof(struct drm_display_mode), GFP_KERNEL); + mode = drm_crtc_mode_create(dev); if (!mode) return NULL; @@ -357,6 +359,7 @@ static struct drm_display_mode established_modes[] = { */ static int add_established_modes(struct drm_output *output, struct edid *edid) { + struct drm_device *dev = output->dev; unsigned long est_bits = edid->established_timings.t1 | (edid->established_timings.t2 << 8) | ((edid->established_timings.mfg_rsvd & 0x80) << 9); @@ -365,7 +368,7 @@ static int add_established_modes(struct drm_output *output, struct edid *edid) for (i = 0; i <= EDID_EST_TIMINGS; i++) if (est_bits & (1<dev; for (i = 0; i < EDID_STD_TIMINGS; i++) { struct std_timing *t = &edid->standard_timings[i]; @@ -391,7 +395,7 @@ static int add_standard_modes(struct drm_output *output, struct edid *edid) continue; drm_mode_probed_add(output, - drm_mode_std(&edid->standard_timings[i])); + drm_mode_std(dev, &edid->standard_timings[i])); modes++; } @@ -409,6 +413,7 @@ static int add_detailed_info(struct drm_output *output, struct edid *edid) { int i, j, modes = 0; bool preferred = 0; + struct drm_device *dev = output->dev; for (i = 0; i < EDID_DETAILED_TIMINGS; i++) { struct detailed_timing *timing = &edid->detailed_timings[i]; @@ -423,7 +428,7 @@ static int add_detailed_info(struct drm_output *output, struct edid *edid) if (i == 0 && edid->preferred_timing) preferred = 1; drm_mode_probed_add(output, - drm_mode_detailed(timing, preferred)); + drm_mode_detailed(dev, timing, preferred)); modes++; continue; } @@ -446,7 +451,7 @@ static int add_detailed_info(struct drm_output *output, struct edid *edid) struct std_timing *std; std = &data->data.timings[j]; - drm_mode_probed_add(output, drm_mode_std(std)); + drm_mode_probed_add(output, drm_mode_std(dev, std)); modes++; } break; -- cgit v1.2.3 From 6f3534a13abb0c8afb157511d0871dbc35bc403d Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Thu, 5 Apr 2007 09:21:31 -0700 Subject: Add copyrights before I forget --- linux-core/drm_edid.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index 3c123751..7e254eee 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2007 Intel Corporation + * Jesse Barnes + */ + #include #include #include "drmP.h" @@ -73,7 +78,7 @@ struct detailed_data_monitor_range { u16 sec_gtf_toggle; /* A000=use above, 20=use below */ u8 hfreq_start_khz; /* need to multiply by 2 */ u8 c; /* need to divide by 2 */ - u16 m; + u16 m; /* FIXME: byte order */ u8 k; u8 j; /* need to divide by 2 */ } __attribute__((packed)); @@ -126,9 +131,9 @@ struct detailed_timing { struct edid { u8 header[8]; /* Vendor & product info */ - u16 mfg_id; - u16 prod_code; - u32 serial; + u16 mfg_id; /* FIXME: byte order */ + u16 prod_code; /* FIXME: byte order */ + u32 serial; /* FIXME: byte order */ u8 mfg_week; u8 mfg_year; /* EDID version */ -- cgit v1.2.3 From 13d4ea90c09fa834eb6eecaa082780aace78dac7 Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Sat, 7 Apr 2007 19:24:09 -0700 Subject: various cleanups to EDID code: - pull in FB DDC code (we'll have to rewrite it anyway it appears) - add comments - note a few FIXMEs - make it less quiet, and more informative when it actually does print --- linux-core/drm_edid.c | 240 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 175 insertions(+), 65 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index 33018da6..fcd97d67 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -1,18 +1,15 @@ /* * Copyright (c) 2007 Intel Corporation * Jesse Barnes + * + * DDC probing routines (drm_ddc_read & drm_do_probe_ddc_edid) originally from + * FB layer. */ - #include -#include +#include #include "drmP.h" -#include "intel_drv.h" - -/* - * DDC/EDID probing rippped off from FB layer - */ -#include "edid.h" +#define EDID_LENGTH 128 #define DDC_ADDR 0x50 #ifdef BIG_ENDIAN @@ -75,7 +72,7 @@ struct detailed_data_monitor_range { u8 min_hfreq_khz; u8 max_hfreq_khz; u8 pixel_clock_mhz; /* need to multiply by 10 */ - u16 sec_gtf_toggle; /* A000=use above, 20=use below */ + u16 sec_gtf_toggle; /* A000=use above, 20=use below */ /* FIXME: byte order */ u8 hfreq_start_khz; /* need to multiply by 2 */ u8 c; /* need to divide by 2 */ u16 m; /* FIXME: byte order */ @@ -114,14 +111,14 @@ struct detailed_non_pixel { } __attribute__((packed)); #define EDID_DETAIL_STD_MODES 0xfa -#define EDID_DETAIL_CPDATA 0xfb -#define EDID_DETAIL_NAME 0xfc -#define EDID_DETAIL_RANGE 0xfd -#define EDID_DETAIL_STRING 0xfe -#define EDID_DETAIL_SERIAL 0xff +#define EDID_DETAIL_MONITOR_CPDATA 0xfb +#define EDID_DETAIL_MONITOR_NAME 0xfc +#define EDID_DETAIL_MONITOR_RANGE 0xfd +#define EDID_DETAIL_MONITOR_STRING 0xfe +#define EDID_DETAIL_MONITOR_SERIAL 0xff struct detailed_timing { - u16 pixel_clock; /* need to multiply by 10 KHz */ + u16 pixel_clock; /* need to multiply by 10 KHz */ /* FIXME: byte order */ union { struct detailed_pixel_timing pixel_data; struct detailed_non_pixel other_data; @@ -184,6 +181,14 @@ struct edid { static u8 edid_header[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; +/** + * edid_valid - sanity check EDID data + * @edid: EDID data + * + * Sanity check the EDID block by looking at the header, the version number + * and the checksum. Return 0 if the EDID doesn't check out, or 1 if it's + * valid. + */ static bool edid_valid(struct edid *edid) { int i; @@ -215,7 +220,8 @@ bad: * Take the standard timing params (in this case width, aspect, and refresh) * and convert them into a real mode using CVT. * - * Punts for now. + * Punts for now, but should eventually use the FB layer's CVT based mode + * generation code. */ struct drm_display_mode *drm_mode_std(struct drm_device *dev, struct std_timing *t) @@ -245,19 +251,28 @@ struct drm_display_mode *drm_mode_std(struct drm_device *dev, return mode; } +/** + * drm_mode_detailed - create a new mode from an EDID detailed timing section + * @timing: EDID detailed timing info + * @preferred: is this a preferred mode? + * + * An EDID detailed timing block contains enough info for us to create and + * return a new struct drm_display_mode. The @preferred flag will be set + * if this is the display's preferred timing, and we'll use it to indicate + * to the other layers that this mode is desired. + */ struct drm_display_mode *drm_mode_detailed(drm_device_t *dev, - struct detailed_timing *timing, - bool preferred) + struct detailed_timing *timing) { struct drm_display_mode *mode; struct detailed_pixel_timing *pt = &timing->data.pixel_data; if (pt->stereo) { - printk(KERN_ERR "stereo mode not supported\n"); + printk(KERN_WARNING "stereo mode not supported\n"); return NULL; } if (!pt->separate_sync) { - printk(KERN_ERR "integrated sync not supported\n"); + printk(KERN_WARNING "integrated sync not supported\n"); return NULL; } @@ -266,7 +281,6 @@ struct drm_display_mode *drm_mode_detailed(drm_device_t *dev, return NULL; mode->type = DRM_MODE_TYPE_DRIVER; - mode->type |= preferred ? DRM_MODE_TYPE_PREFERRED : 0; mode->clock = timing->pixel_clock / 100; mode->hdisplay = (pt->hactive_hi << 8) | pt->hactive_lo; @@ -297,7 +311,10 @@ struct drm_display_mode *drm_mode_detailed(drm_device_t *dev, return mode; } -static struct drm_display_mode established_modes[] = { +/* + * Detailed mode info for the EDID "established modes" data to use. + */ +static struct drm_display_mode edid_est_modes[] = { { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 40000, 800, 840, 968, 1056, 0, 600, 601, 605, 628, 0, V_PHSYNC | V_PVSYNC) }, /* 800x600@60Hz */ @@ -372,8 +389,9 @@ static int add_established_modes(struct drm_output *output, struct edid *edid) for (i = 0; i <= EDID_EST_TIMINGS; i++) if (est_bits & (1<dev; + int i, modes = 0; for (i = 0; i < EDID_STD_TIMINGS; i++) { struct std_timing *t = &edid->standard_timings[i]; + struct drm_display_mode *newmode; /* If std timings bytes are 1, 1 it's empty */ if (t->hsize == 1 && (t->aspect_ratio | t->vfreq) == 1) continue; - drm_mode_probed_add(output, - drm_mode_std(dev, &edid->standard_timings[i])); + newmode = drm_mode_std(dev, &edid->standard_timings[i]); + drm_mode_probed_add(output, newmode); modes++; } @@ -416,13 +435,13 @@ static int add_standard_modes(struct drm_output *output, struct edid *edid) */ static int add_detailed_info(struct drm_output *output, struct edid *edid) { - int i, j, modes = 0; - bool preferred = 0; struct drm_device *dev = output->dev; + int i, j, modes = 0; for (i = 0; i < EDID_DETAILED_TIMINGS; i++) { struct detailed_timing *timing = &edid->detailed_timings[i]; struct detailed_non_pixel *data = &timing->data.other_data; + struct drm_display_mode *newmode; /* EDID up to and including 1.2 may put monitor info here */ if (edid->version == 1 && edid->revision < 3) @@ -430,33 +449,38 @@ static int add_detailed_info(struct drm_output *output, struct edid *edid) /* Detailed mode timing */ if (timing->pixel_clock) { + newmode = drm_mode_detailed(dev, timing); + /* First detailed mode is preferred */ if (i == 0 && edid->preferred_timing) - preferred = 1; - drm_mode_probed_add(output, - drm_mode_detailed(dev, timing, preferred)); + newmode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(output, newmode); + modes++; continue; } /* Other timing or info */ switch (data->type) { - case EDID_DETAIL_SERIAL: + case EDID_DETAIL_MONITOR_SERIAL: break; - case EDID_DETAIL_STRING: + case EDID_DETAIL_MONITOR_STRING: break; - case EDID_DETAIL_RANGE: + case EDID_DETAIL_MONITOR_RANGE: + /* Get monitor range data */ break; - case EDID_DETAIL_NAME: + case EDID_DETAIL_MONITOR_NAME: break; - case EDID_DETAIL_CPDATA: + case EDID_DETAIL_MONITOR_CPDATA: break; case EDID_DETAIL_STD_MODES: /* Five modes per detailed section */ for (j = 0; j < 5; i++) { struct std_timing *std; + struct drm_display_mode *newmode; std = &data->data.timings[j]; - drm_mode_probed_add(output, drm_mode_std(dev, std)); + newmode = drm_mode_std(dev, std); + drm_mode_probed_add(output, newmode); modes++; } break; @@ -468,6 +492,108 @@ static int add_detailed_info(struct drm_output *output, struct edid *edid) return modes; } +#define DDC_ADDR 0x50 + +static unsigned char *drm_do_probe_ddc_edid(struct i2c_adapter *adapter) +{ + unsigned char start = 0x0; + unsigned char *buf = kmalloc(EDID_LENGTH, GFP_KERNEL); + struct i2c_msg msgs[] = { + { + .addr = DDC_ADDR, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = DDC_ADDR, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = buf, + } + }; + + if (!buf) { + dev_warn(&adapter->dev, "unable to allocate memory for EDID " + "block.\n"); + return NULL; + } + + if (i2c_transfer(adapter, msgs, 2) == 2) + return buf; + + dev_info(&adapter->dev, "unable to read EDID block.\n"); + kfree(buf); + return NULL; +} + +static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) +{ + struct i2c_algo_bit_data *algo_data = adapter->algo_data; + unsigned char *edid = NULL; + int i, j; + + /* + * Startup the bus: + * Set clock line high (but give it time to come up) + * Then set clock & data low + */ + algo_data->setscl(algo_data->data, 1); + udelay(550); /* startup delay */ + algo_data->setscl(algo_data->data, 0); + algo_data->setsda(algo_data->data, 0); + + for (i = 0; i < 3; i++) { + /* For some old monitors we need the + * following process to initialize/stop DDC + */ + algo_data->setsda(algo_data->data, 0); + msleep(13); + + algo_data->setscl(algo_data->data, 1); + for (j = 0; j < 5; j++) { + msleep(10); + if (algo_data->getscl(algo_data->data)) + break; + } + if (j == 5) + continue; + + algo_data->setsda(algo_data->data, 0); + msleep(15); + algo_data->setscl(algo_data->data, 0); + msleep(15); + algo_data->setsda(algo_data->data, 1); + msleep(15); + + /* Do the real work */ + edid = drm_do_probe_ddc_edid(adapter); + algo_data->setsda(algo_data->data, 0); + algo_data->setscl(algo_data->data, 0); + msleep(15); + + algo_data->setscl(algo_data->data, 1); + for (j = 0; j < 10; j++) { + msleep(10); + if (algo_data->getscl(algo_data->data)) + break; + } + + algo_data->setsda(algo_data->data, 1); + msleep(15); + algo_data->setscl(algo_data->data, 0); + if (edid) + break; + } + /* Release the DDC lines when done or the Apple Cinema HD display + * will switch off + */ + algo_data->setsda(algo_data->data, 0); + algo_data->setscl(algo_data->data, 0); + algo_data->setscl(algo_data->data, 1); + + return edid; +} + /** * drm_add_edid_modes - add modes from EDID data, if available * @output: output we're probing @@ -481,42 +607,26 @@ static int add_detailed_info(struct drm_output *output, struct edid *edid) int drm_add_edid_modes(struct drm_output *output, struct i2c_adapter *adapter) { struct edid *edid; - u8 *raw_edid; - int i, est_modes, std_modes, det_modes; - - edid = (struct edid *)fb_ddc_read(adapter); + int num_modes = 0; + edid = (struct edid *)drm_ddc_read(adapter); if (!edid) { - dev_warn(&output->dev->pdev->dev, "no EDID data\n"); + dev_warn(&output->dev->pdev->dev, "%s: no EDID data\n", + output->name); goto out_err; } if (!edid_valid(edid)) { - dev_warn(&output->dev->pdev->dev, "EDID invalid, ignoring.\n"); + dev_warn(&output->dev->pdev->dev, "%s: EDID invalid.\n", + output->name); goto out_err; } - est_modes = add_established_modes(output, edid); - std_modes = add_standard_modes(output, edid); - det_modes = add_detailed_info(output, edid); - printk(KERN_ERR "est modes: %d, std_modes: %d, det_modes: %d\n", - est_modes, std_modes, det_modes); - - raw_edid = (u8 *)edid; - printk(KERN_ERR "EDID:\n" KERN_ERR); - for (i = 0; i < EDID_LENGTH; i++) { - if (i != 0 && ((i % 16) == 0)) - printk("\n" KERN_ERR); - printk("%02x", raw_edid[i] & 0xff); - } - printk("\n"); - - printk(KERN_ERR "EDID info:\n"); - printk(KERN_ERR " mfg_id: 0x%04x\n", edid->mfg_id); - printk(KERN_ERR " digital? %s\n", edid->digital ? "Yes" : "No"); - printk(KERN_ERR " extensions: %d\n", edid->extensions); + num_modes += add_established_modes(output, edid); + num_modes += add_standard_modes(output, edid); + num_modes += add_detailed_info(output, edid); - return est_modes + std_modes + det_modes; + return num_modes; out_err: kfree(edid); -- cgit v1.2.3 From 183cbd92dd016f8935f9b58ef9345fde1391173e Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Tue, 10 Apr 2007 09:47:37 -0700 Subject: Finish bringing in LVDS code, re-add to Makefile. Needed other changes too: - move EDID structures to drm_edid.h - add EDID info structure to drm_output - add a few routines to intel_display for getting current mode info - add some prototypes to intel_drv.h and drm_crtc.h --- linux-core/drm_edid.c | 173 +------------------------------------------------- 1 file changed, 3 insertions(+), 170 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index fcd97d67..b79bc2db 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -4,181 +4,14 @@ * * DDC probing routines (drm_ddc_read & drm_do_probe_ddc_edid) originally from * FB layer. + * Copyright (C) 2006 Dennis Munsie */ #include #include #include "drmP.h" +#include "drm_edid.h" -#define EDID_LENGTH 128 -#define DDC_ADDR 0x50 - -#ifdef BIG_ENDIAN -#error "EDID structure is little endian, need big endian versions" -#endif - -struct est_timings { - u8 t1; - u8 t2; - u8 mfg_rsvd; -} __attribute__((packed)); - -struct std_timing { - u8 hsize; /* need to multiply by 8 then add 248 */ - u8 vfreq:6; /* need to add 60 */ - u8 aspect_ratio:2; /* 00=16:10, 01=4:3, 10=5:4, 11=16:9 */ -} __attribute__((packed)); - -/* If detailed data is pixel timing */ -struct detailed_pixel_timing { - u8 hactive_lo; - u8 hblank_lo; - u8 hblank_hi:4; - u8 hactive_hi:4; - u8 vactive_lo; - u8 vblank_lo; - u8 vblank_hi:4; - u8 vactive_hi:4; - u8 hsync_offset_lo; - u8 hsync_pulse_width_lo; - u8 vsync_pulse_width_lo:4; - u8 vsync_offset_lo:4; - u8 hsync_pulse_width_hi:2; - u8 hsync_offset_hi:2; - u8 vsync_pulse_width_hi:2; - u8 vsync_offset_hi:2; - u8 width_mm_lo; - u8 height_mm_lo; - u8 height_mm_hi:4; - u8 width_mm_hi:4; - u8 hborder; - u8 vborder; - u8 unknown0:1; - u8 vsync_positive:1; - u8 hsync_positive:1; - u8 separate_sync:2; - u8 stereo:1; - u8 unknown6:1; - u8 interlaced:1; -} __attribute__((packed)); - -/* If it's not pixel timing, it'll be one of the below */ -struct detailed_data_string { - u8 str[13]; -} __attribute__((packed)); - -struct detailed_data_monitor_range { - u8 min_vfreq; - u8 max_vfreq; - u8 min_hfreq_khz; - u8 max_hfreq_khz; - u8 pixel_clock_mhz; /* need to multiply by 10 */ - u16 sec_gtf_toggle; /* A000=use above, 20=use below */ /* FIXME: byte order */ - u8 hfreq_start_khz; /* need to multiply by 2 */ - u8 c; /* need to divide by 2 */ - u16 m; /* FIXME: byte order */ - u8 k; - u8 j; /* need to divide by 2 */ -} __attribute__((packed)); - -struct detailed_data_wpindex { - u8 white_y_lo:2; - u8 white_x_lo:2; - u8 pad:4; - u8 white_x_hi; - u8 white_y_hi; - u8 gamma; /* need to divide by 100 then add 1 */ -} __attribute__((packed)); - -struct detailed_data_color_point { - u8 windex1; - u8 wpindex1[3]; - u8 windex2; - u8 wpindex2[3]; -} __attribute__((packed)); - -struct detailed_non_pixel { - u8 pad1; - u8 type; /* ff=serial, fe=string, fd=monitor range, fc=monitor name - fb=color point data, fa=standard timing data, - f9=undefined, f8=mfg. reserved */ - u8 pad2; - union { - struct detailed_data_string str; - struct detailed_data_monitor_range range; - struct detailed_data_wpindex color; - struct std_timing timings[5]; - } data; -} __attribute__((packed)); - -#define EDID_DETAIL_STD_MODES 0xfa -#define EDID_DETAIL_MONITOR_CPDATA 0xfb -#define EDID_DETAIL_MONITOR_NAME 0xfc -#define EDID_DETAIL_MONITOR_RANGE 0xfd -#define EDID_DETAIL_MONITOR_STRING 0xfe -#define EDID_DETAIL_MONITOR_SERIAL 0xff - -struct detailed_timing { - u16 pixel_clock; /* need to multiply by 10 KHz */ /* FIXME: byte order */ - union { - struct detailed_pixel_timing pixel_data; - struct detailed_non_pixel other_data; - } data; -} __attribute__((packed)); - -struct edid { - u8 header[8]; - /* Vendor & product info */ - u16 mfg_id; /* FIXME: byte order */ - u16 prod_code; /* FIXME: byte order */ - u32 serial; /* FIXME: byte order */ - u8 mfg_week; - u8 mfg_year; - /* EDID version */ - u8 version; - u8 revision; - /* Display info: */ - /* input definition */ - u8 serration_vsync:1; - u8 sync_on_green:1; - u8 composite_sync:1; - u8 separate_syncs:1; - u8 blank_to_black:1; - u8 video_level:2; - u8 digital:1; /* bits below must be zero if set */ - u8 width_cm; - u8 height_cm; - u8 gamma; - /* feature support */ - u8 default_gtf:1; - u8 preferred_timing:1; - u8 standard_color:1; - u8 display_type:2; /* 00=mono, 01=rgb, 10=non-rgb, 11=unknown */ - u8 pm_active_off:1; - u8 pm_suspend:1; - u8 pm_standby:1; - /* Color characteristics */ - u8 red_green_lo; - u8 black_white_lo; - u8 red_x; - u8 red_y; - u8 green_x; - u8 green_y; - u8 blue_x; - u8 blue_y; - u8 white_x; - u8 white_y; - /* Est. timings and mfg rsvd timings*/ - struct est_timings established_timings; - /* Standard timings 1-8*/ - struct std_timing standard_timings[8]; - /* Detailing timings 1-4 */ - struct detailed_timing detailed_timings[4]; - /* Number of 128 byte ext. blocks */ - u8 extensions; - /* Checksum */ - u8 checksum; -} __attribute__((packed)); - +/* Valid EDID header has these bytes */ static u8 edid_header[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; /** -- cgit v1.2.3 From c731b68091aa7284ee3a89c8a7ea3fdabac45a54 Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Wed, 11 Apr 2007 11:42:00 -0700 Subject: Fix EDID pixel clock calculation. --- linux-core/drm_edid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index b79bc2db..9acdc8da 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -114,7 +114,7 @@ struct drm_display_mode *drm_mode_detailed(drm_device_t *dev, return NULL; mode->type = DRM_MODE_TYPE_DRIVER; - mode->clock = timing->pixel_clock / 100; + mode->clock = timing->pixel_clock * 10; mode->hdisplay = (pt->hactive_hi << 8) | pt->hactive_lo; mode->hsync_start = mode->hdisplay + ((pt->hsync_offset_hi << 8) | -- cgit v1.2.3 From 5587961cfeff86d8368ff03867a1f0667e4a64d4 Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Wed, 18 Apr 2007 11:49:42 -0700 Subject: Document main drm_crtc.c functions, and rename drm_crtc_mode_create to drm_mode_create to be consistent with the other functions. Also document where we need locking fixes and what the locks are for. --- linux-core/drm_edid.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index 9acdc8da..a9cf23a1 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -66,7 +66,7 @@ struct drm_display_mode *drm_mode_std(struct drm_device *dev, struct drm_display_mode *mode; int hsize = t->hsize * 8 + 248, vsize; - mode = drm_crtc_mode_create(dev); + mode = drm_mode_create(dev); if (!mode) return NULL; @@ -109,7 +109,7 @@ struct drm_display_mode *drm_mode_detailed(drm_device_t *dev, return NULL; } - mode = drm_crtc_mode_create(dev); + mode = drm_mode_create(dev); if (!mode) return NULL; -- cgit v1.2.3 From 9ca4932054a5bde5dda500ea346ad101bb5c80a0 Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Fri, 20 Apr 2007 16:32:58 -0700 Subject: Add a mode name generation wrapper to make name format changes easier. --- linux-core/drm_edid.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index a9cf23a1..0d067929 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -79,7 +79,7 @@ struct drm_display_mode *drm_mode_std(struct drm_device *dev, else vsize = (hsize * 9) / 16; - snprintf(mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d", hsize, vsize); + drm_mode_set_name(mode); return mode; } @@ -132,8 +132,7 @@ struct drm_display_mode *drm_mode_detailed(drm_device_t *dev, pt->vsync_pulse_width_lo); mode->vtotal = mode->vdisplay + ((pt->vblank_hi << 8) | pt->vblank_lo); - snprintf(mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d", mode->hdisplay, - mode->vdisplay); + drm_mode_set_name(mode); if (pt->interlaced) mode->flags |= V_INTERLACE; -- cgit v1.2.3 From ffb89d4c3b6650551aaab06076896540a78faddf Mon Sep 17 00:00:00 2001 From: Jerome Glisse Date: Fri, 9 Nov 2007 15:47:24 +0100 Subject: drm: split edid handling in get_edid & add_edid_mode This way driver can get_edid in output status detection (using all workaround which are in get_edid) and then provide this edid data in get_mode callback of output. --- linux-core/drm_edid.c | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index d7d3939a..b8eee1a4 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -427,41 +427,51 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) } /** - * drm_add_edid_modes - add modes from EDID data, if available + * drm_get_edid - get EDID data, if available * @output: output we're probing * @adapter: i2c adapter to use for DDC * - * Poke the given output's i2c channel to grab EDID data if possible. If we - * get any, add the specified modes to the output's mode list. - * - * Return number of modes added or 0 if we couldn't find any. + * Poke the given output's i2c channel to grab EDID data if possible. + * + * Return edid data or NULL if we couldn't find any. */ -int drm_add_edid_modes(struct drm_output *output, struct i2c_adapter *adapter) +struct edid *drm_get_edid(struct drm_output *output, + struct i2c_adapter *adapter) { struct edid *edid; - int num_modes = 0; edid = (struct edid *)drm_ddc_read(adapter); if (!edid) { dev_warn(&output->dev->pdev->dev, "%s: no EDID data\n", output->name); - goto out_err; + return NULL; } - if (!edid_valid(edid)) { dev_warn(&output->dev->pdev->dev, "%s: EDID invalid.\n", output->name); - goto out_err; + kfree(edid); + return NULL; } + return edid; +} +EXPORT_SYMBOL(drm_get_edid); + +/** + * drm_add_edid_modes - add modes from EDID data, if available + * @output: output we're probing + * @edid: edid data + * + * Add the specified modes to the output's mode list. + * + * Return number of modes added or 0 if we couldn't find any. + */ +int drm_add_edid_modes(struct drm_output *output, struct edid *edid) +{ + int num_modes = 0; num_modes += add_established_modes(output, edid); num_modes += add_standard_modes(output, edid); num_modes += add_detailed_info(output, edid); - return num_modes; - -out_err: - kfree(edid); - return 0; } EXPORT_SYMBOL(drm_add_edid_modes); -- cgit v1.2.3 From e1bc147ac9aa8ac2ac271b0a21f4138b17875ce5 Mon Sep 17 00:00:00 2001 From: Jerome Glisse Date: Fri, 9 Nov 2007 17:28:56 +0100 Subject: drm: check edid data, so we deal well with broken driver. --- linux-core/drm_edid.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index b8eee1a4..7068cef3 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -469,6 +469,14 @@ int drm_add_edid_modes(struct drm_output *output, struct edid *edid) { int num_modes = 0; + if (edid == NULL) { + return 0; + } + if (!edid_valid(edid)) { + dev_warn(&output->dev->pdev->dev, "%s: EDID invalid.\n", + output->name); + return 0; + } num_modes += add_established_modes(output, edid); num_modes += add_standard_modes(output, edid); num_modes += add_detailed_info(output, edid); -- cgit v1.2.3 From b13dc383df85d75cb1ea422f4d13efc2a4a8a732 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Tue, 18 Dec 2007 17:41:20 +1100 Subject: remove output names --- linux-core/drm_edid.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index 7068cef3..41aa8f5e 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -443,12 +443,12 @@ struct edid *drm_get_edid(struct drm_output *output, edid = (struct edid *)drm_ddc_read(adapter); if (!edid) { dev_warn(&output->dev->pdev->dev, "%s: no EDID data\n", - output->name); + drm_get_output_name(output)); return NULL; } if (!edid_valid(edid)) { dev_warn(&output->dev->pdev->dev, "%s: EDID invalid.\n", - output->name); + drm_get_output_name(output)); kfree(edid); return NULL; } @@ -474,7 +474,7 @@ int drm_add_edid_modes(struct drm_output *output, struct edid *edid) } if (!edid_valid(edid)) { dev_warn(&output->dev->pdev->dev, "%s: EDID invalid.\n", - output->name); + drm_get_output_name(output)); return 0; } num_modes += add_established_modes(output, edid); -- cgit v1.2.3 From 1a959a2095aef397ea14a6f6cbdf2a035ec0eb5c Mon Sep 17 00:00:00 2001 From: Alan Hourihane Date: Tue, 4 Mar 2008 17:53:04 +0000 Subject: Check mode before adding to EDID --- linux-core/drm_edid.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index 41aa8f5e..9762567b 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -223,8 +223,10 @@ static int add_established_modes(struct drm_output *output, struct edid *edid) if (est_bits & (1<standard_timings[i]); - drm_mode_probed_add(output, newmode); - modes++; + if (newmode) { + drm_mode_probed_add(output, newmode); + modes++; + } } return modes; @@ -283,11 +287,13 @@ static int add_detailed_info(struct drm_output *output, struct edid *edid) if (timing->pixel_clock) { newmode = drm_mode_detailed(dev, timing); /* First detailed mode is preferred */ - if (i == 0 && edid->preferred_timing) - newmode->type |= DRM_MODE_TYPE_PREFERRED; - drm_mode_probed_add(output, newmode); + if (newmode) { + if (i == 0 && edid->preferred_timing) + newmode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(output, newmode); - modes++; + modes++; + } continue; } @@ -312,8 +318,10 @@ static int add_detailed_info(struct drm_output *output, struct edid *edid) std = &data->data.timings[j]; newmode = drm_mode_std(dev, std); - drm_mode_probed_add(output, newmode); - modes++; + if (newmode) { + drm_mode_probed_add(output, newmode); + modes++; + } } break; default: -- cgit v1.2.3 From 0a6e301e6de3421f116d1b5d8205ca4f442091e2 Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Thu, 10 Apr 2008 11:23:55 -0700 Subject: Keep display info in struct display_info Some fields had snuck into the drm_output structure. Put them back and fill in more stuff from the EDID block. --- linux-core/drm_edid.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index 9762567b..e033abdf 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -291,7 +291,11 @@ static int add_detailed_info(struct drm_output *output, struct edid *edid) if (i == 0 && edid->preferred_timing) newmode->type |= DRM_MODE_TYPE_PREFERRED; drm_mode_probed_add(output, newmode); - + + /* Use first one for output's preferred mode */ + if (!output->display_info.preferred_mode) + output->display_info.preferred_mode = + newmode; modes++; } continue; @@ -460,6 +464,9 @@ struct edid *drm_get_edid(struct drm_output *output, kfree(edid); return NULL; } + + output->display_info.raw_edid = (char *)edid; + return edid; } EXPORT_SYMBOL(drm_get_edid); @@ -488,6 +495,25 @@ int drm_add_edid_modes(struct drm_output *output, struct edid *edid) num_modes += add_established_modes(output, edid); num_modes += add_standard_modes(output, edid); num_modes += add_detailed_info(output, edid); + + output->display_info.serration_vsync = edid->serration_vsync; + output->display_info.sync_on_green = edid->sync_on_green; + output->display_info.composite_sync = edid->composite_sync; + output->display_info.separate_syncs = edid->separate_syncs; + output->display_info.blank_to_black = edid->blank_to_black; + output->display_info.video_level = edid->video_level; + output->display_info.digital = edid->digital; + output->display_info.width_mm = edid->width_cm * 10; + output->display_info.height_mm = edid->height_cm * 10; + output->display_info.gamma = edid->gamma; + output->display_info.gtf_supported = edid->default_gtf; + output->display_info.standard_color = edid->standard_color; + output->display_info.display_type = edid->display_type; + output->display_info.active_off_supported = edid->pm_active_off; + output->display_info.suspend_supported = edid->pm_suspend; + output->display_info.standby_supported = edid->pm_standby; + output->display_info.gamma = edid->gamma; + return num_modes; } EXPORT_SYMBOL(drm_add_edid_modes); -- cgit v1.2.3 From 9d38448ed33aaff324cc4bbe1e0878593e97d07d Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Fri, 30 May 2008 15:03:12 +1000 Subject: modesetting: the great renaming. Okay we have crtc, encoder and connectors. No more outputs exposed beyond driver internals I've broken intel tv connector stuff. Really for TV we should have one TV connector, with a sub property for the type of signal been driven over it --- linux-core/drm_edid.c | 94 +++++++++++++++++++++++++-------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index e033abdf..69906ed9 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -211,9 +211,9 @@ static struct drm_display_mode edid_est_modes[] = { * Each EDID block contains a bitmap of the supported "established modes" list * (defined above). Tease them out and add them to the global modes list. */ -static int add_established_modes(struct drm_output *output, struct edid *edid) +static int add_established_modes(struct drm_connector *connector, struct edid *edid) { - struct drm_device *dev = output->dev; + struct drm_device *dev = connector->dev; unsigned long est_bits = edid->established_timings.t1 | (edid->established_timings.t2 << 8) | ((edid->established_timings.mfg_rsvd & 0x80) << 9); @@ -224,7 +224,7 @@ static int add_established_modes(struct drm_output *output, struct edid *edid) struct drm_display_mode *newmode; newmode = drm_mode_duplicate(dev, &edid_est_modes[i]); if (newmode) { - drm_mode_probed_add(output, newmode); + drm_mode_probed_add(connector, newmode); modes++; } } @@ -239,9 +239,9 @@ static int add_established_modes(struct drm_output *output, struct edid *edid) * Standard modes can be calculated using the CVT standard. Grab them from * @edid, calculate them, and add them to the list. */ -static int add_standard_modes(struct drm_output *output, struct edid *edid) +static int add_standard_modes(struct drm_connector *connector, struct edid *edid) { - struct drm_device *dev = output->dev; + struct drm_device *dev = connector->dev; int i, modes = 0; for (i = 0; i < EDID_STD_TIMINGS; i++) { @@ -254,7 +254,7 @@ static int add_standard_modes(struct drm_output *output, struct edid *edid) newmode = drm_mode_std(dev, &edid->standard_timings[i]); if (newmode) { - drm_mode_probed_add(output, newmode); + drm_mode_probed_add(connector, newmode); modes++; } } @@ -269,9 +269,9 @@ static int add_standard_modes(struct drm_output *output, struct edid *edid) * Some of the detailed timing sections may contain mode information. Grab * it and add it to the list. */ -static int add_detailed_info(struct drm_output *output, struct edid *edid) +static int add_detailed_info(struct drm_connector *connector, struct edid *edid) { - struct drm_device *dev = output->dev; + struct drm_device *dev = connector->dev; int i, j, modes = 0; for (i = 0; i < EDID_DETAILED_TIMINGS; i++) { @@ -290,11 +290,11 @@ static int add_detailed_info(struct drm_output *output, struct edid *edid) if (newmode) { if (i == 0 && edid->preferred_timing) newmode->type |= DRM_MODE_TYPE_PREFERRED; - drm_mode_probed_add(output, newmode); + drm_mode_probed_add(connector, newmode); - /* Use first one for output's preferred mode */ - if (!output->display_info.preferred_mode) - output->display_info.preferred_mode = + /* Use first one for connector's preferred mode */ + if (!connector->display_info.preferred_mode) + connector->display_info.preferred_mode = newmode; modes++; } @@ -323,7 +323,7 @@ static int add_detailed_info(struct drm_output *output, struct edid *edid) std = &data->data.timings[j]; newmode = drm_mode_std(dev, std); if (newmode) { - drm_mode_probed_add(output, newmode); + drm_mode_probed_add(connector, newmode); modes++; } } @@ -440,32 +440,32 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) /** * drm_get_edid - get EDID data, if available - * @output: output we're probing + * @connector: connector we're probing * @adapter: i2c adapter to use for DDC * - * Poke the given output's i2c channel to grab EDID data if possible. + * Poke the given connector's i2c channel to grab EDID data if possible. * * Return edid data or NULL if we couldn't find any. */ -struct edid *drm_get_edid(struct drm_output *output, +struct edid *drm_get_edid(struct drm_connector *connector, struct i2c_adapter *adapter) { struct edid *edid; edid = (struct edid *)drm_ddc_read(adapter); if (!edid) { - dev_warn(&output->dev->pdev->dev, "%s: no EDID data\n", - drm_get_output_name(output)); + dev_warn(&connector->dev->pdev->dev, "%s: no EDID data\n", + drm_get_connector_name(connector)); return NULL; } if (!edid_valid(edid)) { - dev_warn(&output->dev->pdev->dev, "%s: EDID invalid.\n", - drm_get_output_name(output)); + dev_warn(&connector->dev->pdev->dev, "%s: EDID invalid.\n", + drm_get_connector_name(connector)); kfree(edid); return NULL; } - output->display_info.raw_edid = (char *)edid; + connector->display_info.raw_edid = (char *)edid; return edid; } @@ -473,14 +473,14 @@ EXPORT_SYMBOL(drm_get_edid); /** * drm_add_edid_modes - add modes from EDID data, if available - * @output: output we're probing + * @connector: connector we're probing * @edid: edid data * - * Add the specified modes to the output's mode list. + * Add the specified modes to the connector's mode list. * * Return number of modes added or 0 if we couldn't find any. */ -int drm_add_edid_modes(struct drm_output *output, struct edid *edid) +int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid) { int num_modes = 0; @@ -488,31 +488,31 @@ int drm_add_edid_modes(struct drm_output *output, struct edid *edid) return 0; } if (!edid_valid(edid)) { - dev_warn(&output->dev->pdev->dev, "%s: EDID invalid.\n", - drm_get_output_name(output)); + dev_warn(&connector->dev->pdev->dev, "%s: EDID invalid.\n", + drm_get_connector_name(connector)); return 0; } - num_modes += add_established_modes(output, edid); - num_modes += add_standard_modes(output, edid); - num_modes += add_detailed_info(output, edid); - - output->display_info.serration_vsync = edid->serration_vsync; - output->display_info.sync_on_green = edid->sync_on_green; - output->display_info.composite_sync = edid->composite_sync; - output->display_info.separate_syncs = edid->separate_syncs; - output->display_info.blank_to_black = edid->blank_to_black; - output->display_info.video_level = edid->video_level; - output->display_info.digital = edid->digital; - output->display_info.width_mm = edid->width_cm * 10; - output->display_info.height_mm = edid->height_cm * 10; - output->display_info.gamma = edid->gamma; - output->display_info.gtf_supported = edid->default_gtf; - output->display_info.standard_color = edid->standard_color; - output->display_info.display_type = edid->display_type; - output->display_info.active_off_supported = edid->pm_active_off; - output->display_info.suspend_supported = edid->pm_suspend; - output->display_info.standby_supported = edid->pm_standby; - output->display_info.gamma = edid->gamma; + num_modes += add_established_modes(connector, edid); + num_modes += add_standard_modes(connector, edid); + num_modes += add_detailed_info(connector, edid); + + connector->display_info.serration_vsync = edid->serration_vsync; + connector->display_info.sync_on_green = edid->sync_on_green; + connector->display_info.composite_sync = edid->composite_sync; + connector->display_info.separate_syncs = edid->separate_syncs; + connector->display_info.blank_to_black = edid->blank_to_black; + connector->display_info.video_level = edid->video_level; + connector->display_info.digital = edid->digital; + connector->display_info.width_mm = edid->width_cm * 10; + connector->display_info.height_mm = edid->height_cm * 10; + connector->display_info.gamma = edid->gamma; + connector->display_info.gtf_supported = edid->default_gtf; + connector->display_info.standard_color = edid->standard_color; + connector->display_info.display_type = edid->display_type; + connector->display_info.active_off_supported = edid->pm_active_off; + connector->display_info.suspend_supported = edid->pm_suspend; + connector->display_info.standby_supported = edid->pm_standby; + connector->display_info.gamma = edid->gamma; return num_modes; } -- cgit v1.2.3 From c987e76d953b6aecbfb69058fc4c387aa3fb33c9 Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Mon, 9 Jun 2008 16:20:45 -0700 Subject: Add EDID quirk handling Port over EDID quirks from X.Org so we can handle more monitors. This meant adding size info to the drm_display_mode struct, but other than that the changes were isolated to the DRM EDID handling code (as they should be). --- linux-core/drm_edid.c | 260 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 237 insertions(+), 23 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index 69906ed9..0d600396 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -1,28 +1,120 @@ /* + * Copyright (c) 2006 Luc Verhaegen (quirks list) * Copyright (c) 2007 Intel Corporation * Jesse Barnes * * DDC probing routines (drm_ddc_read & drm_do_probe_ddc_edid) originally from * FB layer. * Copyright (C) 2006 Dennis Munsie + * + * 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 + * the rights to use, copy, modify, merge, publish, distribute, sub license, + * and/or sell copies of the Software, and to permit persons to whom the + * 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 MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS 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. */ +#include #include #include #include "drmP.h" #include "drm_edid.h" +/* + * TODO: + * - support EDID 1.4 + * - port quirks from X server code + */ + +/* + * EDID blocks out in the wild have a variety of bugs, try to collect + * them here (note that userspace may work around broken monitors first, + * but fixes should make their way here so that the kernel "just works" + * on as many displays as possible). + */ + +/* First detailed mode wrong, use largest 60Hz mode */ +#define EDID_QUIRK_PREFER_LARGE_60 (1 << 0) +/* Reported 135MHz pixel clock is too high, needs adjustment */ +#define EDID_QUIRK_135_CLOCK_TOO_HIGH (1 << 1) +/* Prefer the largest mode at 75 Hz */ +#define EDID_QUIRK_PREFER_LARGE_75 (1 << 2) +/* Detail timing is in cm not mm */ +#define EDID_QUIRK_DETAILED_IN_CM (1 << 3) +/* Detailed timing descriptors have bogus size values, so just take the + * maximum size and use that. + */ +#define EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE (1 << 4) +/* Monitor forgot to set the first detailed is preferred bit. */ +#define EDID_QUIRK_FIRST_DETAILED_PREFERRED (1 << 5) +/* use +hsync +vsync for detailed mode */ +#define EDID_QUIRK_DETAILED_SYNC_PP (1 << 6) + +static struct edid_quirk { + char *vendor; + int product_id; + u32 quirks; +} edid_quirk_list[] = { + /* Acer AL1706 */ + { "ACR", 44358, EDID_QUIRK_PREFER_LARGE_60 }, + /* Acer F51 */ + { "API", 0x7602, EDID_QUIRK_PREFER_LARGE_60 }, + /* Unknown Acer */ + { "ACR", 2423, EDID_QUIRK_FIRST_DETAILED_PREFERRED }, + + /* Belinea 10 15 55 */ + { "MAX", 1516, EDID_QUIRK_PREFER_LARGE_60 }, + { "MAX", 0x77e, EDID_QUIRK_PREFER_LARGE_60 }, + + /* Envision Peripherals, Inc. EN-7100e */ + { "EPI", 59264, EDID_QUIRK_135_CLOCK_TOO_HIGH }, + + /* Funai Electronics PM36B */ + { "FCM", 13600, EDID_QUIRK_PREFER_LARGE_75 | + EDID_QUIRK_DETAILED_IN_CM }, + + /* LG Philips LCD LP154W01-A5 */ + { "LPL", 0, EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE }, + { "LPL", 0x2a00, EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE }, + + /* Philips 107p5 CRT */ + { "PHL", 57364, EDID_QUIRK_FIRST_DETAILED_PREFERRED }, + + /* Proview AY765C */ + { "PTS", 765, EDID_QUIRK_FIRST_DETAILED_PREFERRED }, + + /* Samsung SyncMaster 205BW. Note: irony */ + { "SAM", 541, EDID_QUIRK_DETAILED_SYNC_PP }, + /* Samsung SyncMaster 22[5-6]BW */ + { "SAM", 596, EDID_QUIRK_PREFER_LARGE_60 }, + { "SAM", 638, EDID_QUIRK_PREFER_LARGE_60 }, +}; + + /* Valid EDID header has these bytes */ static u8 edid_header[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; /** - * edid_valid - sanity check EDID data + * edid_is_valid - sanity check EDID data * @edid: EDID data * * Sanity check the EDID block by looking at the header, the version number * and the checksum. Return 0 if the EDID doesn't check out, or 1 if it's * valid. */ -static bool edid_valid(struct edid *edid) +static bool edid_is_valid(struct edid *edid) { int i; u8 csum = 0; @@ -46,6 +138,97 @@ bad: return 0; } +/** + * edid_vendor - match a string against EDID's obfuscated vendor field + * @edid: EDID to match + * @vendor: vendor string + * + * Returns true if @vendor is in @edid, false otherwise + */ +static bool edid_vendor(struct edid *edid, char *vendor) +{ + char edid_vendor[3]; + + edid_vendor[0] = ((edid->mfg_id[0] & 0x7c) >> 2) + '@'; + edid_vendor[1] = (((edid->mfg_id[0] & 0x3) << 3) | + ((edid->mfg_id[1] & 0xe0) >> 5)) + '@'; + edid_vendor[2] = (edid->mfg_id[2] & 0x1f) + '@'; + + return !strncmp(edid_vendor, vendor, 3); +} + +/** + * edid_get_quirks - return quirk flags for a given EDID + * @edid: EDID to process + * + * This tells subsequent routines what fixes they need to apply. + */ +static u32 edid_get_quirks(struct edid *edid) +{ + struct edid_quirk *quirk; + int i; + + for (i = 0; i < ARRAY_SIZE(edid_quirk_list); i++) { + quirk = &edid_quirk_list[i]; + + if (edid_vendor(edid, quirk->vendor) && + (EDID_PRODUCT_ID(edid) == quirk->product_id)) + return quirk->quirks; + } + + return 0; +} + +#define MODE_SIZE(m) ((m)->hdisplay * (m)->vdisplay) +#define MODE_REFRESH_DIFF(m,r) (abs((m)->vrefresh - target_refresh)) + + +/** + * edid_fixup_preferred - set preferred modes based on quirk list + * @connector: has mode list to fix up + * @quirks: quirks list + * + * Walk the mode list for @connector, clearing the preferred status + * on existing modes and setting it anew for the right mode ala @quirks. + */ +static void edid_fixup_preferred(struct drm_connector *connector, + u32 quirks) +{ + struct drm_display_mode *t, *cur_mode, *preferred_mode; + int target_refresh; + + if (list_empty(&connector->probed_modes)) + return; + + if (quirks & EDID_QUIRK_PREFER_LARGE_60) + target_refresh = 60; + if (quirks & EDID_QUIRK_PREFER_LARGE_75) + target_refresh = 75; + + preferred_mode = list_first_entry(&connector->probed_modes, + struct drm_display_mode, head); + + list_for_each_entry_safe(cur_mode, t, &connector->probed_modes, head) { + cur_mode->type &= ~DRM_MODE_TYPE_PREFERRED; + + if (cur_mode == preferred_mode) + continue; + + /* Largest mode is preferred */ + if (MODE_SIZE(cur_mode) > MODE_SIZE(preferred_mode)) + preferred_mode = cur_mode; + + /* At a given size, try to get closest to target refresh */ + if ((MODE_SIZE(cur_mode) == MODE_SIZE(preferred_mode)) && + MODE_REFRESH_DIFF(cur_mode, target_refresh) < + MODE_REFRESH_DIFF(preferred_mode, target_refresh)) { + preferred_mode = cur_mode; + } + } + + preferred_mode->type |= DRM_MODE_TYPE_PREFERRED; +} + /** * drm_mode_std - convert standard mode info (width, height, refresh) into mode * @t: standard timing params @@ -86,16 +269,18 @@ struct drm_display_mode *drm_mode_std(struct drm_device *dev, /** * drm_mode_detailed - create a new mode from an EDID detailed timing section + * @dev: DRM device (needed to create new mode) + * @edid: EDID block * @timing: EDID detailed timing info - * @preferred: is this a preferred mode? + * @quirks: quirks to apply * * An EDID detailed timing block contains enough info for us to create and - * return a new struct drm_display_mode. The @preferred flag will be set - * if this is the display's preferred timing, and we'll use it to indicate - * to the other layers that this mode is desired. + * return a new struct drm_display_mode. */ -struct drm_display_mode *drm_mode_detailed(struct drm_device *dev, - struct detailed_timing *timing) +static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev, + struct edid *edid, + struct detailed_timing *timing, + u32 quirks) { struct drm_display_mode *mode; struct detailed_pixel_timing *pt = &timing->data.pixel_data; @@ -114,6 +299,10 @@ struct drm_display_mode *drm_mode_detailed(struct drm_device *dev, return NULL; mode->type = DRM_MODE_TYPE_DRIVER; + + if (quirks & EDID_QUIRK_135_CLOCK_TOO_HIGH) + timing->pixel_clock = 1088; + mode->clock = timing->pixel_clock * 10; mode->hdisplay = (pt->hactive_hi << 8) | pt->hactive_lo; @@ -137,9 +326,27 @@ struct drm_display_mode *drm_mode_detailed(struct drm_device *dev, if (pt->interlaced) mode->flags |= V_INTERLACE; + if (quirks & EDID_QUIRK_DETAILED_SYNC_PP) { + pt->hsync_positive = 1; + pt->vsync_positive = 1; + } + mode->flags |= pt->hsync_positive ? V_PHSYNC : V_NHSYNC; mode->flags |= pt->vsync_positive ? V_PVSYNC : V_NVSYNC; + mode->width_mm = pt->width_mm_lo | (pt->width_mm_hi << 8); + mode->height_mm = pt->height_mm_lo | (pt->height_mm_hi << 8); + + if (quirks & EDID_QUIRK_DETAILED_IN_CM) { + mode->width_mm *= 10; + mode->height_mm *= 10; + } + + if (quirks & EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE) { + mode->width_mm = edid->width_cm * 10; + mode->height_mm = edid->height_cm * 10; + } + return mode; } @@ -264,12 +471,15 @@ static int add_standard_modes(struct drm_connector *connector, struct edid *edid /** * add_detailed_modes - get detailed mode info from EDID data + * @connector: attached connector * @edid: EDID block to scan + * @quirks: quirks to apply * * Some of the detailed timing sections may contain mode information. Grab * it and add it to the list. */ -static int add_detailed_info(struct drm_connector *connector, struct edid *edid) +static int add_detailed_info(struct drm_connector *connector, + struct edid *edid, u32 quirks) { struct drm_device *dev = connector->dev; int i, j, modes = 0; @@ -285,19 +495,16 @@ static int add_detailed_info(struct drm_connector *connector, struct edid *edid) /* Detailed mode timing */ if (timing->pixel_clock) { - newmode = drm_mode_detailed(dev, timing); + newmode = drm_mode_detailed(dev, edid, timing, quirks); + if (!newmode) + continue; + /* First detailed mode is preferred */ - if (newmode) { - if (i == 0 && edid->preferred_timing) - newmode->type |= DRM_MODE_TYPE_PREFERRED; - drm_mode_probed_add(connector, newmode); + if (i == 0 && edid->preferred_timing) + newmode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, newmode); - /* Use first one for connector's preferred mode */ - if (!connector->display_info.preferred_mode) - connector->display_info.preferred_mode = - newmode; - modes++; - } + modes++; continue; } @@ -458,7 +665,7 @@ struct edid *drm_get_edid(struct drm_connector *connector, drm_get_connector_name(connector)); return NULL; } - if (!edid_valid(edid)) { + if (!edid_is_valid(edid)) { dev_warn(&connector->dev->pdev->dev, "%s: EDID invalid.\n", drm_get_connector_name(connector)); kfree(edid); @@ -483,18 +690,25 @@ EXPORT_SYMBOL(drm_get_edid); int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid) { int num_modes = 0; + u32 quirks; if (edid == NULL) { return 0; } - if (!edid_valid(edid)) { + if (!edid_is_valid(edid)) { dev_warn(&connector->dev->pdev->dev, "%s: EDID invalid.\n", drm_get_connector_name(connector)); return 0; } + + quirks = edid_get_quirks(edid); + num_modes += add_established_modes(connector, edid); num_modes += add_standard_modes(connector, edid); - num_modes += add_detailed_info(connector, edid); + num_modes += add_detailed_info(connector, edid, quirks); + + if (quirks & (EDID_QUIRK_PREFER_LARGE_60 | EDID_QUIRK_PREFER_LARGE_75)) + edid_fixup_preferred(connector, quirks); connector->display_info.serration_vsync = edid->serration_vsync; connector->display_info.sync_on_green = edid->sync_on_green; -- cgit v1.2.3 From 473a1997ace1a9fb545d0457549e50d17eb36175 Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Sun, 22 Jun 2008 16:29:00 +0200 Subject: NV50: Initial import of kernel modesetting. --- linux-core/drm_edid.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index 0d600396..812e64a6 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -122,19 +122,30 @@ static bool edid_is_valid(struct edid *edid) if (memcmp(edid->header, edid_header, sizeof(edid_header))) goto bad; - if (edid->version != 1) + if (edid->version != 1) { + DRM_ERROR("EDID has major version %d, instead of 1\n", edid->version); goto bad; - if (edid->revision <= 0 || edid->revision > 3) + } + if (edid->revision <= 0 || edid->revision > 3) { + DRM_ERROR("EDID has minor version %d, which is not between 0-3\n", edid->revision); goto bad; + } for (i = 0; i < EDID_LENGTH; i++) csum += raw_edid[i]; - if (csum) + if (csum) { + DRM_ERROR("EDID checksum is invalid, remainder is %d\n", csum); goto bad; + } return 1; bad: + if (raw_edid) { + DRM_ERROR("Raw EDID:\n"); + print_hex_dump_bytes(KERN_ERR, DUMP_PREFIX_NONE, raw_edid, EDID_LENGTH); + printk("\n"); + } return 0; } @@ -545,7 +556,7 @@ static int add_detailed_info(struct drm_connector *connector, #define DDC_ADDR 0x50 -static unsigned char *drm_do_probe_ddc_edid(struct i2c_adapter *adapter) +unsigned char *drm_do_probe_ddc_edid(struct i2c_adapter *adapter) { unsigned char start = 0x0; unsigned char *buf = kmalloc(EDID_LENGTH, GFP_KERNEL); @@ -576,6 +587,7 @@ static unsigned char *drm_do_probe_ddc_edid(struct i2c_adapter *adapter) kfree(buf); return NULL; } +EXPORT_SYMBOL(drm_do_probe_ddc_edid); static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) { @@ -589,15 +601,15 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) * Then set clock & data low */ algo_data->setscl(algo_data->data, 1); - udelay(550); /* startup delay */ - algo_data->setscl(algo_data->data, 0); - algo_data->setsda(algo_data->data, 0); + //udelay(550); /* startup delay */ + //algo_data->setscl(algo_data->data, 0); + //algo_data->setsda(algo_data->data, 0); for (i = 0; i < 3; i++) { /* For some old monitors we need the * following process to initialize/stop DDC */ - algo_data->setsda(algo_data->data, 0); + algo_data->setsda(algo_data->data, 1); msleep(13); algo_data->setscl(algo_data->data, 1); @@ -632,16 +644,16 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) algo_data->setsda(algo_data->data, 1); msleep(15); algo_data->setscl(algo_data->data, 0); + algo_data->setsda(algo_data->data, 0); if (edid) break; } /* Release the DDC lines when done or the Apple Cinema HD display * will switch off */ - algo_data->setsda(algo_data->data, 0); - algo_data->setscl(algo_data->data, 0); + algo_data->setsda(algo_data->data, 1); algo_data->setscl(algo_data->data, 1); - + return edid; } -- cgit v1.2.3 From 3809209349ccf12aa71c7848f0b21d77fa0a5f03 Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Sun, 22 Jun 2008 17:01:30 +0200 Subject: Undo something i didn't want to change. - I made it consistent with recent kernel fb code (maybe this is older bugged code?) - Still i don't use this and i should leave it to others. --- linux-core/drm_edid.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index 812e64a6..22c6e3bf 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -601,15 +601,15 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) * Then set clock & data low */ algo_data->setscl(algo_data->data, 1); - //udelay(550); /* startup delay */ - //algo_data->setscl(algo_data->data, 0); - //algo_data->setsda(algo_data->data, 0); + udelay(550); /* startup delay */ + algo_data->setscl(algo_data->data, 0); + algo_data->setsda(algo_data->data, 0); for (i = 0; i < 3; i++) { /* For some old monitors we need the * following process to initialize/stop DDC */ - algo_data->setsda(algo_data->data, 1); + algo_data->setsda(algo_data->data, 0); msleep(13); algo_data->setscl(algo_data->data, 1); @@ -644,7 +644,6 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) algo_data->setsda(algo_data->data, 1); msleep(15); algo_data->setscl(algo_data->data, 0); - algo_data->setsda(algo_data->data, 0); if (edid) break; } -- cgit v1.2.3 From e810cb9243fe6c4905182869d9e3272d861a14cb Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Sun, 6 Jul 2008 10:52:25 +0200 Subject: modesetting-101: rename modeflags, as to avoid conflicts with the xorg definitions --- linux-core/drm_edid.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index 22c6e3bf..a3d9861f 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -335,15 +335,15 @@ static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev, drm_mode_set_name(mode); if (pt->interlaced) - mode->flags |= V_INTERLACE; + mode->flags |= DRM_MODE_FLAG_INTERLACE; if (quirks & EDID_QUIRK_DETAILED_SYNC_PP) { pt->hsync_positive = 1; pt->vsync_positive = 1; } - mode->flags |= pt->hsync_positive ? V_PHSYNC : V_NHSYNC; - mode->flags |= pt->vsync_positive ? V_PVSYNC : V_NVSYNC; + mode->flags |= pt->hsync_positive ? DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC; + mode->flags |= pt->vsync_positive ? DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC; mode->width_mm = pt->width_mm_lo | (pt->width_mm_hi << 8); mode->height_mm = pt->height_mm_lo | (pt->height_mm_hi << 8); @@ -367,55 +367,55 @@ static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev, static struct drm_display_mode edid_est_modes[] = { { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 40000, 800, 840, 968, 1056, 0, 600, 601, 605, 628, 0, - V_PHSYNC | V_PVSYNC) }, /* 800x600@60Hz */ + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@60Hz */ { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 36000, 800, 824, 896, 1024, 0, 600, 601, 603, 625, 0, - V_PHSYNC | V_PVSYNC) }, /* 800x600@56Hz */ + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@56Hz */ { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 656, 720, 840, 0, 480, 481, 484, 500, 0, - V_NHSYNC | V_NVSYNC) }, /* 640x480@75Hz */ + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@75Hz */ { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 664, 704, 832, 0, 480, 489, 491, 520, 0, - V_NHSYNC | V_NVSYNC) }, /* 640x480@72Hz */ + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@72Hz */ { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 30240, 640, 704, 768, 864, 0, 480, 483, 486, 525, 0, - V_NHSYNC | V_NVSYNC) }, /* 640x480@67Hz */ + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@67Hz */ { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25200, 640, 656, 752, 800, 0, 480, 490, 492, 525, 0, - V_NHSYNC | V_NVSYNC) }, /* 640x480@60Hz */ + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@60Hz */ { DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 35500, 720, 738, 846, 900, 0, 400, 421, 423, 449, 0, - V_NHSYNC | V_NVSYNC) }, /* 720x400@88Hz */ + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 720x400@88Hz */ { DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 28320, 720, 738, 846, 900, 0, 400, 412, 414, 449, 0, - V_NHSYNC | V_PVSYNC) }, /* 720x400@70Hz */ + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 720x400@70Hz */ { DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 135000, 1280, 1296, 1440, 1688, 0, 1024, 1025, 1028, 1066, 0, - V_PHSYNC | V_PVSYNC) }, /* 1280x1024@75Hz */ + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1280x1024@75Hz */ { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 78800, 1024, 1040, 1136, 1312, 0, 768, 769, 772, 800, 0, - V_PHSYNC | V_PVSYNC) }, /* 1024x768@75Hz */ + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1024x768@75Hz */ { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 75000, 1024, 1048, 1184, 1328, 0, 768, 771, 777, 806, 0, - V_NHSYNC | V_NVSYNC) }, /* 1024x768@70Hz */ + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 1024x768@70Hz */ { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 65000, 1024, 1048, 1184, 1344, 0, 768, 771, 777, 806, 0, - V_NHSYNC | V_NVSYNC) }, /* 1024x768@60Hz */ + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 1024x768@60Hz */ { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER,44900, 1024, 1032, 1208, 1264, 0, 768, 768, 776, 817, 0, - V_PHSYNC | V_PVSYNC | V_INTERLACE) }, /* 1024x768@43Hz */ + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_INTERLACE) }, /* 1024x768@43Hz */ { DRM_MODE("832x624", DRM_MODE_TYPE_DRIVER, 57284, 832, 864, 928, 1152, 0, 624, 625, 628, 667, 0, - V_NHSYNC | V_NVSYNC) }, /* 832x624@75Hz */ + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 832x624@75Hz */ { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 49500, 800, 816, 896, 1056, 0, 600, 601, 604, 625, 0, - V_PHSYNC | V_PVSYNC) }, /* 800x600@75Hz */ + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@75Hz */ { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 50000, 800, 856, 976, 1040, 0, 600, 637, 643, 666, 0, - V_PHSYNC | V_PVSYNC) }, /* 800x600@72Hz */ + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@72Hz */ { DRM_MODE("1152x864", DRM_MODE_TYPE_DRIVER, 108000, 1152, 1216, 1344, 1600, 0, 864, 865, 868, 900, 0, - V_PHSYNC | V_PVSYNC) }, /* 1152x864@75Hz */ + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1152x864@75Hz */ }; #define EDID_EST_TIMINGS 16 -- cgit v1.2.3 From 0b7d9a97bd2383fe4382fc1b1b266542020f0c4e Mon Sep 17 00:00:00 2001 From: Alan Hourihane Date: Mon, 7 Jul 2008 15:11:48 +0100 Subject: Synchronize the DDC EDID read to it's fb_ddc.c counterpart --- linux-core/drm_edid.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'linux-core/drm_edid.c') diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index a3d9861f..07894720 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -595,21 +595,13 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) unsigned char *edid = NULL; int i, j; - /* - * Startup the bus: - * Set clock line high (but give it time to come up) - * Then set clock & data low - */ algo_data->setscl(algo_data->data, 1); - udelay(550); /* startup delay */ - algo_data->setscl(algo_data->data, 0); - algo_data->setsda(algo_data->data, 0); for (i = 0; i < 3; i++) { /* For some old monitors we need the * following process to initialize/stop DDC */ - algo_data->setsda(algo_data->data, 0); + algo_data->setsda(algo_data->data, 1); msleep(13); algo_data->setscl(algo_data->data, 1); @@ -644,6 +636,7 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) algo_data->setsda(algo_data->data, 1); msleep(15); algo_data->setscl(algo_data->data, 0); + algo_data->setsda(algo_data->data, 0); if (edid) break; } -- cgit v1.2.3