blob: c7b2077180c4bd73d531703dca512275b7964447 [file] [log] [blame]
/*
* linux/drivers/video/fbmon.c
*
* Copyright (C) 2002 James Simmons <jsimmons@users.sf.net>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*
*/
#include <linux/tty.h>
#include <linux/fb.h>
#include <linux/module.h>
#ifdef CONFIG_PCI
#include <linux/pci.h>
#endif
/*
* EDID parser
*
* portions of this file were based on the EDID parser by
* John Fremlin <vii@users.sourceforge.net> and Ani Joshi <ajoshi@unixbox.com>
*/
#define EDID_LENGTH 0x80
#define EDID_HEADER 0x00
#define EDID_HEADER_END 0x07
#define ID_MANUFACTURER_NAME 0x08
#define ID_MANUFACTURER_NAME_END 0x09
#define ID_MODEL 0x0a
#define ID_SERIAL_NUMBER 0x0c
#define MANUFACTURE_WEEK 0x10
#define MANUFACTURE_YEAR 0x11
#define EDID_STRUCT_VERSION 0x12
#define EDID_STRUCT_REVISION 0x13
#define DPMS_FLAGS 0x18
#define ESTABLISHED_TIMING_1 0x23
#define ESTABLISHED_TIMING_2 0x24
#define MANUFACTURERS_TIMINGS 0x25
#define DETAILED_TIMING_DESCRIPTIONS_START 0x36
#define DETAILED_TIMING_DESCRIPTION_SIZE 18
#define NO_DETAILED_TIMING_DESCRIPTIONS 4
#define DETAILED_TIMING_DESCRIPTION_1 0x36
#define DETAILED_TIMING_DESCRIPTION_2 0x48
#define DETAILED_TIMING_DESCRIPTION_3 0x5a
#define DETAILED_TIMING_DESCRIPTION_4 0x6c
#define DESCRIPTOR_DATA 5
#define UPPER_NIBBLE( x ) \
(((128|64|32|16) & (x)) >> 4)
#define LOWER_NIBBLE( x ) \
((1|2|4|8) & (x))
#define COMBINE_HI_8LO( hi, lo ) \
( (((unsigned)hi) << 8) | (unsigned)lo )
#define COMBINE_HI_4LO( hi, lo ) \
( (((unsigned)hi) << 4) | (unsigned)lo )
#define PIXEL_CLOCK_LO (unsigned)block[ 0 ]
#define PIXEL_CLOCK_HI (unsigned)block[ 1 ]
#define PIXEL_CLOCK (COMBINE_HI_8LO( PIXEL_CLOCK_HI,PIXEL_CLOCK_LO )*1000)
#define H_ACTIVE_LO (unsigned)block[ 2 ]
#define H_BLANKING_LO (unsigned)block[ 3 ]
#define H_ACTIVE_HI UPPER_NIBBLE( (unsigned)block[ 4 ] )
#define H_ACTIVE COMBINE_HI_8LO( H_ACTIVE_HI, H_ACTIVE_LO )
#define H_BLANKING_HI LOWER_NIBBLE( (unsigned)block[ 4 ] )
#define H_BLANKING COMBINE_HI_8LO( H_BLANKING_HI, H_BLANKING_LO )
#define V_ACTIVE_LO (unsigned)block[ 5 ]
#define V_BLANKING_LO (unsigned)block[ 6 ]
#define V_ACTIVE_HI UPPER_NIBBLE( (unsigned)block[ 7 ] )
#define V_ACTIVE COMBINE_HI_8LO( V_ACTIVE_HI, V_ACTIVE_LO )
#define V_BLANKING_HI LOWER_NIBBLE( (unsigned)block[ 7 ] )
#define V_BLANKING COMBINE_HI_8LO( V_BLANKING_HI, V_BLANKING_LO )
#define H_SYNC_OFFSET_LO (unsigned)block[ 8 ]
#define H_SYNC_WIDTH_LO (unsigned)block[ 9 ]
#define V_SYNC_OFFSET_LO UPPER_NIBBLE( (unsigned)block[ 10 ] )
#define V_SYNC_WIDTH_LO LOWER_NIBBLE( (unsigned)block[ 10 ] )
#define V_SYNC_WIDTH_HI ((unsigned)block[ 11 ] & (1|2))
#define V_SYNC_OFFSET_HI (((unsigned)block[ 11 ] & (4|8)) >> 2)
#define H_SYNC_WIDTH_HI (((unsigned)block[ 11 ] & (16|32)) >> 4)
#define H_SYNC_OFFSET_HI (((unsigned)block[ 11 ] & (64|128)) >> 6)
#define V_SYNC_WIDTH COMBINE_HI_4LO( V_SYNC_WIDTH_HI, V_SYNC_WIDTH_LO )
#define V_SYNC_OFFSET COMBINE_HI_4LO( V_SYNC_OFFSET_HI, V_SYNC_OFFSET_LO )
#define H_SYNC_WIDTH COMBINE_HI_4LO( H_SYNC_WIDTH_HI, H_SYNC_WIDTH_LO )
#define H_SYNC_OFFSET COMBINE_HI_4LO( H_SYNC_OFFSET_HI, H_SYNC_OFFSET_LO )
#define H_SIZE_LO (unsigned)block[ 12 ]
#define V_SIZE_LO (unsigned)block[ 13 ]
#define H_SIZE_HI UPPER_NIBBLE( (unsigned)block[ 14 ] )
#define V_SIZE_HI LOWER_NIBBLE( (unsigned)block[ 14 ] )
#define H_SIZE COMBINE_HI_8LO( H_SIZE_HI, H_SIZE_LO )
#define V_SIZE COMBINE_HI_8LO( V_SIZE_HI, V_SIZE_LO )
#define H_BORDER (unsigned)block[ 15 ]
#define V_BORDER (unsigned)block[ 16 ]
#define FLAGS (unsigned)block[ 17 ]
#define INTERLACED (FLAGS&128)
#define SYNC_TYPE (FLAGS&3<<3) /* bits 4,3 */
#define SYNC_SEPARATE (3<<3)
#define HSYNC_POSITIVE (FLAGS & 4)
#define VSYNC_POSITIVE (FLAGS & 2)
const unsigned char edid_v1_header[] = { 0x00, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x00
};
const unsigned char edid_v1_descriptor_flag[] = { 0x00, 0x00 };
static int edid_checksum(unsigned char *edid)
{
unsigned char i, csum = 0;
for (i = 0; i < EDID_LENGTH; i++)
csum += edid[i];
if (csum == 0x00) {
/* checksum passed, everything's good */
return 1;
} else {
printk("EDID checksum failed, aborting\n");
return 0;
}
}
static int edid_check_header(unsigned char *edid)
{
if ((edid[0] != 0x00) || (edid[1] != 0xff) || (edid[2] != 0xff) ||
(edid[3] != 0xff) || (edid[4] != 0xff) || (edid[5] != 0xff) ||
(edid[6] != 0xff)) {
printk
("EDID header doesn't match EDID v1 header, aborting\n");
return 0;
}
return 1;
}
static char *edid_get_vendor(unsigned char *block)
{
static char sign[4];
unsigned short h;
h = COMBINE_HI_8LO(block[0], block[1]);
sign[0] = ((h >> 10) & 0x1f) + 'A' - 1;
sign[1] = ((h >> 5) & 0x1f) + 'A' - 1;
sign[2] = (h & 0x1f) + 'A' - 1;
sign[3] = 0;
return sign;
}
static char *edid_get_monitor(unsigned char *block)
{
static char name[13];
unsigned i;
const unsigned char *ptr = block + DESCRIPTOR_DATA;
for (i = 0; i < 13; i++, ptr++) {
if (*ptr == 0xa) {
name[i] = 0x00;
return name;
}
name[i] = *ptr;
}
return name;
}
static int edid_is_timing_block(unsigned char *block)
{
if ((block[0] == 0x00) && (block[1] == 0x00))
return 0;
else
return 1;
}
static int edid_is_monitor_block(unsigned char *block)
{
if ((block[0] == 0x00) && (block[1] == 0x00) && (block[3] == 0xfc))
return 1;
else
return 0;
}
static void parse_timing_block(unsigned char *block,
struct fb_var_screeninfo *var)
{
var->xres = var->xres_virtual = H_ACTIVE;
var->yres = var->yres_virtual = V_ACTIVE;
var->height = var->width = -1;
var->right_margin = H_SYNC_OFFSET;
var->left_margin = (H_ACTIVE + H_BLANKING) -
(H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH);
var->upper_margin = V_BLANKING - V_SYNC_OFFSET - V_SYNC_WIDTH;
var->lower_margin = V_SYNC_OFFSET;
var->hsync_len = H_SYNC_WIDTH;
var->vsync_len = V_SYNC_WIDTH;
var->pixclock = PIXEL_CLOCK;
var->pixclock /= 1000;
var->pixclock = KHZ2PICOS(var->pixclock);
if (HSYNC_POSITIVE)
var->sync |= FB_SYNC_HOR_HIGH_ACT;
if (VSYNC_POSITIVE)
var->sync |= FB_SYNC_VERT_HIGH_ACT;
}
int parse_edid(unsigned char *edid, struct fb_var_screeninfo *var)
{
unsigned char *block, *vendor, *monitor = NULL;
int i;
if (!(edid_checksum(edid)))
return 0;
if (!(edid_check_header(edid)))
return 0;
printk("EDID ver %d rev %d\n", (int) edid[EDID_STRUCT_VERSION],
(int) edid[EDID_STRUCT_REVISION]);
vendor = edid_get_vendor(edid + ID_MANUFACTURER_NAME);
block = edid + DETAILED_TIMING_DESCRIPTIONS_START;
for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) {
if (edid_is_monitor_block(block)) {
monitor = edid_get_monitor(block);
}
}
printk("EDID: detected %s %s\n", vendor, monitor);
block = edid + DETAILED_TIMING_DESCRIPTIONS_START;
for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) {
if (edid_is_timing_block(block)) {
parse_timing_block(block, var);
}
}
return 1;
}
#ifdef CONFIG_PCI
char *get_EDID(struct pci_dev *pdev)
{
#ifdef CONFIG_ALL_PPC
static char *propnames[] =
{ "DFP,EDID", "LCD,EDID", "EDID", "EDID1", NULL };
unsigned char *pedid = NULL;
struct device_node *dp;
int i;
dp = pci_device_to_OF_node(pdev);
while (dp != NULL) {
for (i = 0; propnames[i] != NULL; ++i) {
pedid =
(unsigned char *) get_property(dp,
propnames[i],
NULL);
if (pedid != NULL)
return pedid;
}
dp = dp->child;
}
return pedid;
#else
return NULL;
#endif
}
#endif
EXPORT_SYMBOL(parse_edid);
#ifdef CONFIG_PCI
EXPORT_SYMBOL(get_EDID);
#endif