/* * 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; } } } }