⛏️ index : haiku.git

/*
 * i2c interface.
 * Bus should be run at max. 100kHz: see original Philips I2C specification
 *	
 * Rudolf Cornelissen 12/2002-4/2021
 */

#define MODULE_BIT 0x00004000

#include "nv_std.h"

static void i2c_DumpSpecsEDID(edid_specs* specs);

char i2c_flag_error (char ErrNo)
//error code list:
//0 - OK status
//1 - SCL locked low by device (bus is still busy)
//2 - SDA locked low by device (bus is still busy)
//3 - No Acknowledge from device (no handshake)
//4 - SDA not released for master to generate STOP bit
{
	static char I2CError = 0;

	if (!I2CError) I2CError = ErrNo;
	if (ErrNo == -1) I2CError = 0;
	return I2CError;
}

static void i2c_select_bus_set(bool set)
{
	/* I/O pins set selection is only valid on dualhead cards */
	if (!si->ps.secondary_head) return;

	/* select GPU I/O pins set to connect to I2C 'registers' */
	if (set) {
		/* this setup wires the 'I2C registers' to unknown I/O pins on the GPU? */
		NV_REG32(NV32_FUNCSEL) &= ~0x00000010;
		NV_REG32(NV32_2FUNCSEL) |= 0x00000010;
	} else {
		/* this setup wires the 'I2C registers' to the I2C buses */
		NV_REG32(NV32_2FUNCSEL) &= ~0x00000010;
		NV_REG32(NV32_FUNCSEL) |= 0x00000010;
	}
}

static void OutSCL(uint8 BusNR, bool Bit)
{
	uint8 data;
	uint32 data32;

	if ((CFGR(DEVID) & 0xfff0ffff) == 0x024010de) {
		/* C51 chipset */	
		switch (BusNR) {
		case 0:
			data32 = NV_REG32(NV32_NV4E_I2CBUS_0) & ~0x2f;
			if (Bit)
				NV_REG32(NV32_NV4E_I2CBUS_0) = data32 | 0x21;
			else
				NV_REG32(NV32_NV4E_I2CBUS_0) = data32 | 0x01;
			break;
		case 1:
			data32 = NV_REG32(NV32_NV4E_I2CBUS_1) & ~0x2f;
			if (Bit)
				NV_REG32(NV32_NV4E_I2CBUS_1) = data32 | 0x21;
			else
				NV_REG32(NV32_NV4E_I2CBUS_1) = data32 | 0x01;
			break;
		case 2:
			data32 = NV_REG32(NV32_NV4E_I2CBUS_2) & ~0x2f;
			if (Bit)
				NV_REG32(NV32_NV4E_I2CBUS_2) = data32 | 0x21;
			else
				NV_REG32(NV32_NV4E_I2CBUS_2) = data32 | 0x01;
			break;
		}
	} else {
		switch (BusNR) {
		case 0:
			data = (CRTCR(WR_I2CBUS_0) & 0xf0) | 0x01;
			if (Bit)
				CRTCW(WR_I2CBUS_0, (data | 0x20));
			else
				CRTCW(WR_I2CBUS_0, (data & ~0x20));
			break;
		case 1:
			data = (CRTCR(WR_I2CBUS_1) & 0xf0) | 0x01;
			if (Bit)
				CRTCW(WR_I2CBUS_1, (data | 0x20));
			else
				CRTCW(WR_I2CBUS_1, (data & ~0x20));
			break;
		case 2:
			data = (CRTCR(WR_I2CBUS_2) & 0xf0) | 0x01;
			if (Bit)
				CRTCW(WR_I2CBUS_2, (data | 0x20));
			else
				CRTCW(WR_I2CBUS_2, (data & ~0x20));
			break;
		}
	}
}

static void OutSDA(uint8 BusNR, bool Bit)
{
	uint8 data;
	uint32 data32;
	
	if ((CFGR(DEVID) & 0xfff0ffff) == 0x024010de) {
		/* C51 chipset */	
		switch (BusNR) {
		case 0:
			data32 = NV_REG32(NV32_NV4E_I2CBUS_0) & ~0x1f;
			if (Bit)
				NV_REG32(NV32_NV4E_I2CBUS_0) = data32 | 0x11;
			else
				NV_REG32(NV32_NV4E_I2CBUS_0) = data32 | 0x01;
			break;
		case 1:
			data32 = NV_REG32(NV32_NV4E_I2CBUS_1) & ~0x1f;
			if (Bit)
				NV_REG32(NV32_NV4E_I2CBUS_1) = data32 | 0x11;
			else
				NV_REG32(NV32_NV4E_I2CBUS_1) = data32 | 0x01;
			break;
		case 2:
			data32 = NV_REG32(NV32_NV4E_I2CBUS_2) & ~0x1f;
			if (Bit)
				NV_REG32(NV32_NV4E_I2CBUS_2) = data32 | 0x11;
			else
				NV_REG32(NV32_NV4E_I2CBUS_2) = data32 | 0x01;
			break;
		}
	} else {
		switch (BusNR) {
		case 0:
			data = (CRTCR(WR_I2CBUS_0) & 0xf0) | 0x01;
			if (Bit)
				CRTCW(WR_I2CBUS_0, (data | 0x10));
			else
				CRTCW(WR_I2CBUS_0, (data & ~0x10));
			break;
		case 1:
			data = (CRTCR(WR_I2CBUS_1) & 0xf0) | 0x01;
			if (Bit)
				CRTCW(WR_I2CBUS_1, (data | 0x10));
			else
				CRTCW(WR_I2CBUS_1, (data & ~0x10));
			break;
		case 2:
			data = (CRTCR(WR_I2CBUS_2) & 0xf0) | 0x01;
			if (Bit)
				CRTCW(WR_I2CBUS_2, (data | 0x10));
			else
				CRTCW(WR_I2CBUS_2, (data & ~0x10));
			break;
		}
	}
}

static bool InSCL(uint8 BusNR)
{
	if ((CFGR(DEVID) & 0xfff0ffff) == 0x024010de) {
		/* C51 chipset */	
		switch (BusNR) {
		case 0:
			if (NV_REG32(NV32_NV4E_I2CBUS_0) & 0x00040000) return true;
			break;
		case 1:
			if (NV_REG32(NV32_NV4E_I2CBUS_1) & 0x00040000) return true;
			break;
		case 2:
			if (NV_REG32(NV32_NV4E_I2CBUS_2) & 0x00040000) return true;
			break;
		}
	} else {
		switch (BusNR) {
		case 0:
			if ((CRTCR(RD_I2CBUS_0) & 0x04)) return true;
			break;
		case 1:
			if ((CRTCR(RD_I2CBUS_1) & 0x04)) return true;
			break;
		case 2:
			if ((CRTCR(RD_I2CBUS_2) & 0x04)) return true;
			break;
		}
	}

	return false;
}

static bool InSDA(uint8 BusNR)
{
	if ((CFGR(DEVID) & 0xfff0ffff) == 0x024010de) {
		/* C51 chipset */	
		switch (BusNR) {
		case 0:
			if (NV_REG32(NV32_NV4E_I2CBUS_0) & 0x00080000) return true;
			break;
		case 1:
			if (NV_REG32(NV32_NV4E_I2CBUS_1) & 0x00080000) return true;
			break;
		case 2:
			if (NV_REG32(NV32_NV4E_I2CBUS_2) & 0x00080000) return true;
			break;
		}
	} else {
		switch (BusNR) {
		case 0:
			if ((CRTCR(RD_I2CBUS_0) & 0x08)) return true;
			break;
		case 1:
			if ((CRTCR(RD_I2CBUS_1) & 0x08)) return true;
			break;
		case 2:
			if ((CRTCR(RD_I2CBUS_2) & 0x08)) return true;
			break;
		}
	}

	return false;
}

static void TXBit (uint8 BusNR, bool Bit)
{
	/* send out databit */
	if (Bit) {
		OutSDA(BusNR, true);
		snooze(3);
		if (!InSDA(BusNR)) i2c_flag_error (2);
	} else {
		OutSDA(BusNR, false);
	}
	/* generate clock pulse */
	snooze(6);
	OutSCL(BusNR, true);
	snooze(3);
	if (!InSCL(BusNR)) i2c_flag_error (1);
	snooze(6);
	OutSCL(BusNR, false);
	snooze(6);
}

static uint8 RXBit (uint8 BusNR)
{
	uint8 Bit = 0;

	/* set SDA so input is possible */
	OutSDA(BusNR, true);
	/* generate clock pulse */
	snooze(6);
	OutSCL(BusNR, true);
	snooze(3);
	if (!InSCL(BusNR)) i2c_flag_error (1);
	snooze(3);
	/* read databit */
	if (InSDA(BusNR)) Bit = 1;
	/* finish clockpulse */
	OutSCL(BusNR, false);
	snooze(6);

	return Bit;
}

void i2c_bstart (uint8 BusNR)
{
	/* enable access to primary head */
	set_crtc_owner(0);

	/* make sure SDA is high */
	OutSDA(BusNR, true);
	snooze(3);
	OutSCL(BusNR, true);
	snooze(3);
	if (!InSCL(BusNR)) i2c_flag_error (1);
	snooze(6);
	/* clear SDA while SCL set (bus-start condition) */
	OutSDA(BusNR, false);
	snooze(6);
	OutSCL(BusNR, false);
	snooze(6);

	LOG(4,("I2C: START condition generated on bus %d; status is %d\n",
		BusNR, i2c_flag_error (0)));
}

void i2c_bstop (uint8 BusNR)
{
	/* enable access to primary head */
	set_crtc_owner(0);

	/* make sure SDA is low */
	OutSDA(BusNR, false);
	snooze(3);
	OutSCL(BusNR, true);
	snooze(3);
	if (!InSCL(BusNR)) i2c_flag_error (1);
	snooze(6);
	/* set SDA while SCL set (bus-stop condition) */
	OutSDA(BusNR, true);
	snooze(3);
	if (!InSDA(BusNR)) i2c_flag_error (4);
	snooze(3);

	LOG(4,("I2C: STOP condition generated on bus %d; status is %d\n",
		BusNR, i2c_flag_error (0)));
}

uint8 i2c_readbyte(uint8 BusNR, bool Ack)
{
	uint8 cnt, bit, byte = 0;

	/* enable access to primary head */
	set_crtc_owner(0);

	/* read data */
	for (cnt = 8; cnt > 0; cnt--) {
		byte <<= 1;
		bit = RXBit (BusNR);
		byte += bit;
	}
	/* send acknowledge */
	TXBit (BusNR, Ack);

	LOG(4,("I2C: read byte ($%02x) from bus #%d; status is %d\n",
		byte, BusNR, i2c_flag_error(0)));

	return byte;
}

bool i2c_writebyte (uint8 BusNR, uint8 byte)
{
	uint8 cnt;
	bool bit;
	uint8 tmp = byte;

	/* enable access to primary head */
	set_crtc_owner(0);

	/* write data */
	for (cnt = 8; cnt > 0; cnt--) {
		bit = (tmp & 0x80);
		TXBit (BusNR, bit);
		tmp <<= 1;
	}
	/* read acknowledge */
	bit = RXBit (BusNR);
	if (bit) i2c_flag_error (3);

	LOG(4,("I2C: written byte ($%02x) to bus #%d; status is %d\n",
		byte, BusNR, i2c_flag_error(0)));

	return bit;
}

void i2c_readbuffer (uint8 BusNR, uint8* buf, uint8 size)
{
	uint8 cnt;

	for (cnt = 0; cnt < size; cnt++)
		buf[cnt] = i2c_readbyte(BusNR, buf[cnt]);
}

void i2c_writebuffer (uint8 BusNR, uint8* buf, uint8 size)
{
	uint8 cnt;

	for (cnt = 0; cnt < size; cnt++)
		i2c_writebyte(BusNR, buf[cnt]);
}

status_t i2c_init(void)
{
	uint8 bus, buses;
	bool *i2c_bus = &(si->ps.i2c_bus0);
	status_t result = B_ERROR;

	LOG(4,("I2C: searching for wired I2C buses...\n"));

	/* select GPU I/O pins for I2C buses */
	i2c_select_bus_set(false);

	/* enable access to primary head */
	set_crtc_owner(0);

	/* on some NV40 architecture cards the i2c busses can be disabled: enable them */
	if (si->ps.card_arch == NV40A)
		CRTCW(I2C_LOCK ,(CRTCR(I2C_LOCK) | 0x04));

	/* preset no board wired buses */
	si->ps.i2c_bus0 = false;
	si->ps.i2c_bus1 = false;
	si->ps.i2c_bus2 = false;

	/* set number of buses to test for */
	buses = 2;

	/* newer cards (can) have a third bus.. */
	if (((si->ps.card_arch == NV10A) && (si->ps.card_type >= NV17)) || (si->ps.card_arch >= NV30A))
		buses = 3;

	/* find existing buses */
	for (bus = 0; bus < buses; bus++) {
		/* reset status */
		i2c_flag_error (-1);
		snooze(6);
		/* init and/or stop I2C bus */
		i2c_bstop(bus);
		/* check for hardware coupling of SCL and SDA -out and -in lines */
		snooze(6);
		OutSCL(bus, false);
		snooze(3);
		OutSDA(bus, true);
		snooze(3);
		if (InSCL(bus) || !InSDA(bus)) continue;
		snooze(3);
		OutSCL(bus, true);
		snooze(3);
		OutSDA(bus, false);
		snooze(3);
		if (!InSCL(bus) || InSDA(bus)) continue;
		i2c_bus[bus] = true;
		snooze(3);
		/* re-init bus */
		i2c_bstop(bus);
	}

	for (bus = 0; bus < buses; bus++) {
		if (i2c_bus[bus]) {
			LOG(4,("I2C: bus #%d wiring check: passed\n", bus));
			result = B_OK;
		} else {
			LOG(4,("I2C: bus #%d wiring check: failed\n", bus));
		}
	}

	i2c_DetectScreens();
	LOG(4,("I2C: dumping EDID specs for connector 1:\n"));
	i2c_DumpSpecsEDID(&si->ps.con1_screen);
	LOG(4,("I2C: dumping EDID specs for connector 2:\n"));
	i2c_DumpSpecsEDID(&si->ps.con2_screen);

	return result;
}

/*** DDC/EDID library use ***/
typedef struct {
	uint8 port;
} ddc_port_info;

/* Dump EDID info in driver's logfile */
static void
i2c_DumpEDID(edid1_info *edid)
{
	int i, j;

	LOG(4,("Vendor: %s\n", edid->vendor.manufacturer));
	LOG(4,("Product ID: %d\n", (int)edid->vendor.prod_id));
	LOG(4,("Serial #: %d\n", (int)edid->vendor.serial));
	LOG(4,("Produced in week/year: %d/%d\n", edid->vendor.week, edid->vendor.year));

	LOG(4,("EDID version: %d.%d\n", edid->version.version, edid->version.revision));

	LOG(4,("Type: %s\n", edid->display.input_type ? "Digital" : "Analog"));
	LOG(4,("Size: %d cm x %d cm\n", edid->display.h_size, edid->display.v_size));
	LOG(4,("Gamma=%.3f\n", (edid->display.gamma + 100) / 100.0));
	LOG(4,("White (X,Y)=(%.3f,%.3f)\n", edid->display.white_x / 1024.0,
		edid->display.white_y / 1024.0));

	LOG(4,("Supported Future Video Modes:\n"));
	for (i = 0; i < EDID1_NUM_STD_TIMING; ++i) {
		if (edid->std_timing[i].h_size <= 256)
			continue;

		LOG(4,("%dx%d@%dHz (id=%d)\n", 
			edid->std_timing[i].h_size, edid->std_timing[i].v_size,
			edid->std_timing[i].refresh, edid->std_timing[i].id));
	}

	LOG(4,("Supported VESA Video Modes:\n"));
	if (edid->established_timing.res_720x400x70)
		LOG(4,("720x400@70\n"));
	if (edid->established_timing.res_720x400x88)
		LOG(4,("720x400@88\n"));
	if (edid->established_timing.res_640x480x60)
		LOG(4,("640x480@60\n"));
	if (edid->established_timing.res_640x480x67)
		LOG(4,("640x480x67\n"));
	if (edid->established_timing.res_640x480x72)
		LOG(4,("640x480x72\n"));
	if (edid->established_timing.res_640x480x75)
		LOG(4,("640x480x75\n"));
	if (edid->established_timing.res_800x600x56)
		LOG(4,("800x600@56\n"));
	if (edid->established_timing.res_800x600x60)
		LOG(4,("800x600@60\n"));

	if (edid->established_timing.res_800x600x72)
		LOG(4,("800x600@72\n"));
	if (edid->established_timing.res_800x600x75)
		LOG(4,("800x600@75\n"));
	if (edid->established_timing.res_832x624x75)
		LOG(4,("832x624@75\n"));
	if (edid->established_timing.res_1024x768x87i)
		LOG(4,("1024x768@87 interlaced\n"));
	if (edid->established_timing.res_1024x768x60)
		LOG(4,("1024x768@60\n"));
	if (edid->established_timing.res_1024x768x70)
		LOG(4,("1024x768@70\n"));
	if (edid->established_timing.res_1024x768x75)
		LOG(4,("1024x768@75\n"));
	if (edid->established_timing.res_1280x1024x75)
		LOG(4,("1280x1024@75\n"));

	if (edid->established_timing.res_1152x870x75)
		LOG(4,("1152x870@75\n"));

	for (i = 0; i < EDID1_NUM_DETAILED_MONITOR_DESC; ++i) {
		edid1_detailed_monitor *monitor = &edid->detailed_monitor[i];

		switch(monitor->monitor_desc_type) {
			case EDID1_SERIAL_NUMBER:
				LOG(4,("Serial Number: %s\n", monitor->data.serial_number));
				break;

			case EDID1_ASCII_DATA:
				LOG(4,("Ascii Data: %s\n", monitor->data.ascii_data));
				break;

			case EDID1_MONITOR_RANGES:
			{
				edid1_monitor_range monitor_range = monitor->data.monitor_range;

				LOG(4,("Horizontal frequency range = %d..%d kHz\n",
					monitor_range.min_h, monitor_range.max_h));
				LOG(4,("Vertical frequency range = %d..%d Hz\n",
					monitor_range.min_v, monitor_range.max_v));
				LOG(4,("Maximum pixel clock = %d MHz\n", (uint16)monitor_range.max_clock * 10));
				break;
			}

			case EDID1_MONITOR_NAME:
				LOG(4,("Monitor Name: %s\n", monitor->data.monitor_name));
				break;

			case EDID1_ADD_COLOUR_POINTER:
			{
				for (j = 0; j < EDID1_NUM_EXTRA_WHITEPOINTS; ++j) {
					edid1_whitepoint *whitepoint = &monitor->data.whitepoint[j];

					if (whitepoint->index == 0)
						continue;

					LOG(4,("Additional whitepoint: (X,Y)=(%f,%f) gamma=%f index=%i\n",
						whitepoint->white_x / 1024.0, 
						whitepoint->white_y / 1024.0, 
						(whitepoint->gamma + 100) / 100.0, 
						whitepoint->index));
				}
				break;
			}

			case EDID1_ADD_STD_TIMING:
			{		
				for (j = 0; j < EDID1_NUM_EXTRA_STD_TIMING; ++j) {
					edid1_std_timing *timing = &monitor->data.std_timing[j];

					if (timing->h_size <= 256)
						continue;

					LOG(4,("%dx%d@%dHz (id=%d)\n", 
						timing->h_size, timing->v_size,
						timing->refresh, timing->id));
				}
				break;
			}

			case EDID1_IS_DETAILED_TIMING:
			{
				edid1_detailed_timing *timing = &monitor->data.detailed_timing;

				LOG(4,("Additional Video Mode:\n"));
				LOG(4,("clock=%f MHz\n", timing->pixel_clock / 100.0));
				LOG(4,("h: (%d, %d, %d, %d)\n", 
					timing->h_active, timing->h_active + timing->h_sync_off,
					timing->h_active + timing->h_sync_off + timing->h_sync_width,
					timing->h_active + timing->h_blank));
				LOG(4,("v: (%d, %d, %d, %d)\n", 
					timing->v_active, timing->v_active + timing->v_sync_off,
					timing->v_active + timing->v_sync_off + timing->v_sync_width,
					timing->v_active + timing->v_blank));
				LOG(4,("size: %.1f cm x %.1f cm\n", 
					timing->h_size / 10.0, timing->v_size / 10.0));
				LOG(4,("border: %.1f cm x %.1f cm\n",
					timing->h_border / 10.0, timing->v_border / 10.0));
				break;
			}
		}
	}
}

/* callback for getting signals from I2C bus */
static status_t
get_signals(void *cookie, int *clk, int *data)
{
	ddc_port_info *info = (ddc_port_info *)cookie;

	*clk = *data = 0x0000;
	if (InSCL(info->port)) *clk = 0x0001;
	if (InSDA(info->port)) *data = 0x0001;

	return B_OK;
}

/* callback for setting signals on I2C bus */
static status_t
set_signals(void *cookie, int clk, int data)
{
	ddc_port_info *info = (ddc_port_info *)cookie;

	if (clk)
		OutSCL(info->port, true);
	else
		OutSCL(info->port, false);

	if (data)
		OutSDA(info->port, true);
	else
		OutSDA(info->port, false);

	return B_OK;
}

/* Read EDID information from monitor via the display data channel (DDC) */
static status_t
i2c_ReadEDID(uint8 BusNR, edid1_info *edid)
{
	i2c_bus bus;
	ddc_port_info info;

	info.port = BusNR;

	bus.cookie = &info;
	bus.set_signals = &set_signals;
	bus.get_signals = &get_signals;
	ddc2_init_timing(&bus);

	/* select GPU I/O pins for I2C buses */
	i2c_select_bus_set(false);

	/* enable access to primary head */
	set_crtc_owner(0);

	if (ddc2_read_edid1(&bus, edid, NULL, NULL) == B_OK) {
		LOG(4,("I2C: EDID succesfully read from monitor at bus %d\n", BusNR));
		LOG(4,("I2C: EDID dump follows (bus %d):\n", BusNR));
		i2c_DumpEDID(edid);
		LOG(4,("I2C: end EDID dump (bus %d).\n", BusNR));
	} else {
		LOG(4,("I2C: reading EDID failed at bus %d!\n", BusNR));
		return B_ERROR;
	}

	return B_OK;
}

void i2c_TestEDID(void)
{
	uint8 bus;
	edid1_info edid;
	bool *i2c_bus = &(si->ps.i2c_bus0);

	/* test wired bus(es) */
	for (bus = 0; bus < 3; bus++) {
		if (i2c_bus[bus])
			i2c_ReadEDID(bus, &edid);
	}
}

static status_t
i2c_ExtractSpecsEDID(edid1_info* edid, edid_specs* specs)
{
	uint32 i;
	edid1_detailed_timing edid_timing;

	specs->have_full_edid = false;
	specs->have_native_edid = false;
	specs->timing.h_display = 0;
	specs->timing.v_display = 0;

	/* find the optimum (native) modeline */
	for (i = 0; i < EDID1_NUM_DETAILED_MONITOR_DESC; ++i) {
		switch(edid->detailed_monitor[i].monitor_desc_type) {
		case EDID1_IS_DETAILED_TIMING:
			// TODO: handle flags correctly!
			edid_timing = edid->detailed_monitor[i].data.detailed_timing;

			if (edid_timing.pixel_clock <= 0/* || edid_timing.sync != 3*/)
				break;

			/* we want the optimum (native) modeline only, widescreen if possible.
			 * So only check for horizontal display, not for vertical display. */
			if (edid_timing.h_active <= specs->timing.h_display)
				break;

			specs->timing.pixel_clock = edid_timing.pixel_clock * 10;
			specs->timing.h_display = edid_timing.h_active;
			specs->timing.h_sync_start = edid_timing.h_active + edid_timing.h_sync_off;
			specs->timing.h_sync_end = specs->timing.h_sync_start + edid_timing.h_sync_width;
			specs->timing.h_total = specs->timing.h_display + edid_timing.h_blank;
			specs->timing.v_display = edid_timing.v_active;
			specs->timing.v_sync_start = edid_timing.v_active + edid_timing.v_sync_off;
			specs->timing.v_sync_end = specs->timing.v_sync_start + edid_timing.v_sync_width;
			specs->timing.v_total = specs->timing.v_display + edid_timing.v_blank;
			specs->timing.flags = 0;
			if (edid_timing.sync == 3) {
				if (edid_timing.misc & 1)
					specs->timing.flags |= B_POSITIVE_HSYNC;
				if (edid_timing.misc & 2)
					specs->timing.flags |= B_POSITIVE_VSYNC;
			}
			if (edid_timing.interlaced)
				specs->timing.flags |= B_TIMING_INTERLACED;
			break;
		}
	}

	/* check if we actually got a modeline */
	if (!specs->timing.h_display || !specs->timing.v_display) return B_ERROR;

	/* check if the mode is at least VGA. If it's not, ignore specs */
	if ((specs->timing.h_display < 640) || (specs->timing.v_display < 480)) {
		LOG(4,("I2C: specsEDID: screen reports lower than VGA native mode, ignoring specs!\n"));
		return B_ERROR;
	}

	/* determine screen aspect ratio */
	specs->aspect =
		(specs->timing.h_display / ((float)specs->timing.v_display));

	/* determine connection type */
	specs->digital = false;
	if (edid->display.input_type) specs->digital = true;

	/* and also copy full edid1_info for reference */
	memcpy(&(specs->full_edid), edid, sizeof(specs->full_edid));

	/* we succesfully fetched the specs we need */
	specs->have_native_edid = true;
	/* we also got full and valid EDID via DDC */
	specs->have_full_edid = true;

	return B_OK;
}

/* Dump EDID info in driver's logfile */
static void
i2c_DumpSpecsEDID(edid_specs* specs)
{
	LOG(4,("I2C: specsEDID: have_native_edid: %s\n", specs->have_native_edid ? "True" : "False"));
	if (!specs->have_native_edid) return;
	LOG(4,("I2C: specsEDID: timing.pixel_clock %.3f Mhz\n", specs->timing.pixel_clock / 1000.0));
	LOG(4,("I2C: specsEDID: timing.h_display %d\n", specs->timing.h_display));
	LOG(4,("I2C: specsEDID: timing.h_sync_start %d\n", specs->timing.h_sync_start));
	LOG(4,("I2C: specsEDID: timing.h_sync_end %d\n", specs->timing.h_sync_end));
	LOG(4,("I2C: specsEDID: timing.h_total %d\n", specs->timing.h_total));
	LOG(4,("I2C: specsEDID: timing.v_display %d\n", specs->timing.v_display));
	LOG(4,("I2C: specsEDID: timing.v_sync_start %d\n", specs->timing.v_sync_start));
	LOG(4,("I2C: specsEDID: timing.v_sync_end %d\n", specs->timing.v_sync_end));
	LOG(4,("I2C: specsEDID: timing.v_total %d\n", specs->timing.v_total));
	LOG(4,("I2C: specsEDID: timing.flags $%08x\n", specs->timing.flags));
	LOG(4,("I2C: specsEDID: aspect: %1.2f\n", specs->aspect));
	LOG(4,("I2C: specsEDID: digital: %s\n", specs->digital ? "True" : "False"));
}

/* notes:
 * - con1 resides closest to the mainboard on for example NV25 and NV28, while for
 *   example on NV34 con2 sits closest to the mainboard.
 * - i2c bus0 is connected to con1, and i2c bus1 is connected to con2 on all pre-NV40
 *   architecture cards. On later cards it's vice versa. These connections do not depend
 *   on the analog VGA switch setting (see nv_general_output_select()). It also does
 *   not depend on the way screens are connected to the cards (DVI/VGA, 1 or 2 screens).
 * - on some NV40 architecture cards i2c bus2 connects to con2 instead of i2c bus0. This
 *   is confirmed on GeForce FX 6600 (NV43, id 0x0141) and GeForce 7300 (G72, id 0x01d1). 
 * - on pre-NV40 laptops i2c bus2 can connect to con2 as well: confirmed on a Geforce FX
 *   5200 Go (NV34, id 0x0324).
 * - con1 has CRTC1 and DAC1, and con2 has CRTC2 and DAC2 if nv_general_output_select()
 *   is set to 'straight' and there are only VGA type screens connected. */
void i2c_DetectScreens(void)
{
	edid1_info edid;

	si->ps.con1_screen.have_native_edid = false;
	si->ps.con2_screen.have_native_edid = false;
	si->ps.con1_screen.have_full_edid = false;
	si->ps.con2_screen.have_full_edid = false;
	si->ps.con1_screen.aspect = 0;
	si->ps.con2_screen.aspect = 0;

	/* check existance of bus 0 */
	if (si->ps.i2c_bus0) {
		/* check I2C bus 0 for an EDID capable screen */
		if (i2c_ReadEDID(0, &edid) == B_OK) {
			/* fetch optimum (native) modeline */
			switch (si->ps.card_arch) {
			case NV40A:
				i2c_ExtractSpecsEDID(&edid, &si->ps.con2_screen);
				break;
			default:
				i2c_ExtractSpecsEDID(&edid, &si->ps.con1_screen);
				break;
			}
		}
	}

	/* check existance of bus 1 */
	if (si->ps.i2c_bus1) {
		/* check I2C bus 1 for an EDID screen */
		if (i2c_ReadEDID(1, &edid) == B_OK) {
			/* fetch optimum (native) modeline */
			switch (si->ps.card_arch) {
			case NV40A:
				i2c_ExtractSpecsEDID(&edid, &si->ps.con1_screen);
				break;
			default:
				i2c_ExtractSpecsEDID(&edid, &si->ps.con2_screen);
				break;
			}
		}
	}

	/* check existance of bus 2 */
	if (si->ps.i2c_bus2) {
		/* check I2C bus 2 for an EDID screen */
		if (i2c_ReadEDID(2, &edid) == B_OK) {
			/* fetch optimum (native) modeline */
			switch (si->ps.card_arch) {
			case NV40A:
				if (!si->ps.con2_screen.have_native_edid) {
					i2c_ExtractSpecsEDID(&edid, &si->ps.con2_screen);
				} else {
					LOG(4,("I2C: DetectScreens: WARNING, unexpected behaviour detected!\n"));
				}
				break;
			default:
				if (!si->ps.con2_screen.have_native_edid && si->ps.laptop) {
					i2c_ExtractSpecsEDID(&edid, &si->ps.con2_screen);
				} else {
					LOG(4,("I2C: DetectScreens: WARNING, unexpected behaviour detected!\n"));
				}
				break;
			}
		}
	}
}