/*++ Copyright (c) 1990-2001 Microsoft Corporation Module Name: i2c2.c Abstract: This is the NT Video port I2C helper code for interface version 2. Author: Michael Maciesowicz (mmacie) 23-Apr-2001 Environment: kernel mode only Notes: --*/ #include "videoprt.h" // // Define constants used by I2C. // #define I2C_START_RETRIES 10 #define I2C_SCL_READ_RETRIES 10 #define I2C_DELAY(pI2CControl) DELAY_MICROSECONDS((pI2CControl)->I2CDelay / 10) #ifdef ALLOC_PRAGMA #pragma alloc_text (PAGE, I2CStart2) #pragma alloc_text (PAGE, I2CStop2) #pragma alloc_text (PAGE, I2CWrite2) #pragma alloc_text (PAGE, I2CRead2) #pragma alloc_text (PAGE, I2CWriteByte2) #pragma alloc_text (PAGE, I2CReadByte2) #pragma alloc_text (PAGE, I2CWaitForClockLineHigh2) #endif // ALLOC_PRAGMA // // Routines exported via VideoPortQueryServices(). // BOOLEAN I2CStart2( IN PVOID pHwDeviceExtension, IN PVIDEO_I2C_CONTROL pI2CControl ) /*++ Routine Description: This routine starts I2C communication. Arguments: pHwDeviceExtension - Points to per-adapter device extension. pI2CControl - I2C hardware specific functions. Returns: TRUE - Start OK. FALSE - Start failed. --*/ { ULONG ulRetry; PAGED_CODE(); ASSERT(NULL != pHwDeviceExtension); ASSERT(NULL != pI2CControl); ASSERT(NULL != pI2CControl->WriteClockLine); ASSERT(NULL != pI2CControl->WriteDataLine); ASSERT(NULL != pI2CControl->ReadClockLine); ASSERT(NULL != pI2CControl->ReadDataLine); ASSERT(IS_HW_DEVICE_EXTENSION(pHwDeviceExtension) == TRUE); // // The I2C communications start signal is a SDA high->low while the SCL is high. // for (ulRetry = 0; ulRetry <= I2C_START_RETRIES; ulRetry++) { pI2CControl->WriteDataLine(pHwDeviceExtension, 1); // Set SDA high I2C_DELAY(pI2CControl); if (pI2CControl->ReadDataLine(pHwDeviceExtension) == FALSE) // SDA didn't take - ulRetry continue; pI2CControl->WriteClockLine(pHwDeviceExtension, 1); // Set SCL high I2C_DELAY(pI2CControl); if (I2CWaitForClockLineHigh2(pHwDeviceExtension, pI2CControl) == FALSE) { pVideoDebugPrint((Warn, "VIDEOPRT: I2CStart2: SCL didn't take\n")); break; } pI2CControl->WriteDataLine(pHwDeviceExtension, 0); // Set SDA low I2C_DELAY(pI2CControl); pI2CControl->WriteClockLine(pHwDeviceExtension, 0); // Set SCL low I2C_DELAY(pI2CControl); return TRUE; } pVideoDebugPrint((Warn, "VIDEOPRT: I2CStart2: Failed\n")); return FALSE; } // I2CStart2() BOOLEAN I2CStop2( IN PVOID pHwDeviceExtension, IN PVIDEO_I2C_CONTROL pI2CControl ) /*++ Routine Description: This routine stops I2C communication. Arguments: pHwDeviceExtension - Points to per-adapter device extension. pI2CControl - I2C hardware specific functions. Returns: TRUE - Stop OK. FALSE - Stop failed. --*/ { PAGED_CODE(); ASSERT(NULL != pHwDeviceExtension); ASSERT(NULL != pI2CControl); ASSERT(NULL != pI2CControl->WriteClockLine); ASSERT(NULL != pI2CControl->WriteDataLine); ASSERT(NULL != pI2CControl->ReadClockLine); ASSERT(NULL != pI2CControl->ReadDataLine); ASSERT(IS_HW_DEVICE_EXTENSION(pHwDeviceExtension) == TRUE); // // The I2C communications stop signal is a SDA low->high while the SCL is high. // pI2CControl->WriteDataLine(pHwDeviceExtension, 0); // Set SDA low I2C_DELAY(pI2CControl); pI2CControl->WriteClockLine(pHwDeviceExtension, 1); // Set SCL high I2C_DELAY(pI2CControl); if (I2CWaitForClockLineHigh2(pHwDeviceExtension, pI2CControl) == FALSE) { pVideoDebugPrint((Warn, "VIDEOPRT: I2CStop2: SCL didn't take\n")); return FALSE; } pI2CControl->WriteDataLine(pHwDeviceExtension, 1); // Set SDA high I2C_DELAY(pI2CControl); if (pI2CControl->ReadDataLine(pHwDeviceExtension) != 1) { pVideoDebugPrint((Warn, "VIDEOPRT: I2CStop2: SDA didn't take\n")); return FALSE; } return TRUE; } // I2CStop2() BOOLEAN I2CWrite2( IN PVOID pHwDeviceExtension, IN PVIDEO_I2C_CONTROL pI2CControl, IN PUCHAR pucBuffer, IN ULONG ulLength ) /*++ Routine Description: This routine writes data over the I2C channel. Arguments: pHwDeviceExtension - Points to per-adapter device extension. pI2CControl - I2C hardware specific functions. pucBuffer - Points to data to be written. ulLength - Number of bytes to write. Returns: TRUE - Write OK. FALSE - Write failed. --*/ { ULONG ulCount; PAGED_CODE(); ASSERT(NULL != pHwDeviceExtension); ASSERT(NULL != pI2CControl); ASSERT(NULL != pucBuffer); ASSERT(NULL != pI2CControl->WriteClockLine); ASSERT(NULL != pI2CControl->WriteDataLine); ASSERT(NULL != pI2CControl->ReadClockLine); ASSERT(NULL != pI2CControl->ReadDataLine); ASSERT(IS_HW_DEVICE_EXTENSION(pHwDeviceExtension) == TRUE); for (ulCount = 0; ulCount < ulLength; ulCount++) { if (I2CWriteByte2(pHwDeviceExtension, pI2CControl, pucBuffer[ulCount]) == FALSE) { return FALSE; } } return TRUE; } // I2CWrite2() BOOLEAN I2CRead2( IN PVOID pHwDeviceExtension, IN PVIDEO_I2C_CONTROL pI2CControl, OUT PUCHAR pucBuffer, IN ULONG ulLength, IN BOOLEAN bEndOfRead ) /*++ Routine Description: This routine reads data over the I2C channel. Arguments: pHwDeviceExtension - Points to per-adapter device extension. pI2CControl - I2C hardware specific functions. pucBuffer - Points to storage for data. ulLength - Number of bytes to read. bEndOfRead - Indicates end of read requests so we can send NAK. Returns: TRUE - Read OK. FALSE - Read failed. --*/ { ULONG ulCount; PAGED_CODE(); ASSERT(NULL != pHwDeviceExtension); ASSERT(NULL != pI2CControl); ASSERT(NULL != pucBuffer); ASSERT(NULL != pI2CControl->WriteClockLine); ASSERT(NULL != pI2CControl->WriteDataLine); ASSERT(NULL != pI2CControl->ReadClockLine); ASSERT(NULL != pI2CControl->ReadDataLine); ASSERT(IS_HW_DEVICE_EXTENSION(pHwDeviceExtension) == TRUE); // // On all but the last byte, we must send an ACK in order to ensure that the sending device will // send subsequent data bytes. On the last byte, we must send a NAK so that it will shut up. // for (ulCount = 0; ulCount < ulLength; ulCount++) { if ((ulLength - 1 == ulCount && bEndOfRead)) { if (I2CReadByte2(pHwDeviceExtension, pI2CControl, pucBuffer + ulCount, TRUE) == FALSE) // Last byte { return FALSE; } } else { if (I2CReadByte2(pHwDeviceExtension, pI2CControl, pucBuffer + ulCount, FALSE) == FALSE) { return FALSE; } } } return TRUE; } // I2CRead2() // // Local routines. // BOOLEAN I2CWriteByte2( IN PVOID pHwDeviceExtension, IN PVIDEO_I2C_CONTROL pI2CControl, IN UCHAR ucByte ) /*++ Routine Description: This routine writes byte over the I2C channel. Arguments: pHwDeviceExtension - Points to per-adapter device extension. pI2CControl - I2C hardware specific functions. ucByte - Byte to write. Returns: TRUE - Write OK. FALSE - Write failed. --*/ { LONG lShift; UCHAR ucAck; PAGED_CODE(); ASSERT(NULL != pHwDeviceExtension); ASSERT(NULL != pI2CControl); // // Bits are transmitted serially starting with the MSB. // for (lShift = 7; lShift >= 0; lShift--) { // // Transmitt data bit. // pI2CControl->WriteDataLine(pHwDeviceExtension, (UCHAR)((ucByte >> lShift) & 0x01)); // Set SDA I2C_DELAY(pI2CControl); // // After each data bit we must send high->low SCL pulse. // pI2CControl->WriteClockLine(pHwDeviceExtension, 1); // Set SCL high I2C_DELAY(pI2CControl); if (I2CWaitForClockLineHigh2(pHwDeviceExtension, pI2CControl) == FALSE) { pVideoDebugPrint((Warn, "VIDEOPRT: I2CWriteByte2: SCL didn't take\n")); return FALSE; } pI2CControl->WriteClockLine(pHwDeviceExtension, 0); // Set SCL low I2C_DELAY(pI2CControl); } // // The monitor sends ACK by preventing the SDA from going high after the clock pulse we use // to send our last data bit. If the SDA goes high after this bit, it is a NAK from the monitor. // pI2CControl->WriteDataLine(pHwDeviceExtension, 1); // Set SDA high I2C_DELAY(pI2CControl); pI2CControl->WriteClockLine(pHwDeviceExtension, 1); // Set SCL high I2C_DELAY(pI2CControl); if (I2CWaitForClockLineHigh2(pHwDeviceExtension, pI2CControl) == FALSE) { pVideoDebugPrint((Warn, "VIDEOPRT: I2CWriteByte2: SCL didn't take - ACK failed\n")); return FALSE; } ucAck = pI2CControl->ReadDataLine(pHwDeviceExtension); // Read ACK bit pI2CControl->WriteClockLine(pHwDeviceExtension, 0); // Set SCL low I2C_DELAY(pI2CControl); if (1 == ucAck) // NAK from the monitor { pVideoDebugPrint((Warn, "VIDEOPRT: I2CWriteByte2: NAK received\n")); return FALSE; } return TRUE; } // I2CWriteByte2() BOOLEAN I2CReadByte2( IN PVOID pHwDeviceExtension, IN PVIDEO_I2C_CONTROL pI2CControl, OUT PUCHAR pucByte, IN BOOLEAN bEndOfRead ) /*++ Routine Description: This routine reads byte over the I2C channel. Arguments: pHwDeviceExtension - Points to per-adapter device extension. pI2CControl - I2C hardware specific functions. pucBuffer - Points to storage for data. bEndOfRead - TRUE if this is last byte to read. Returns: TRUE - Read OK. FALSE - Read failed. --*/ { LONG lShift; PAGED_CODE(); ASSERT(NULL != pHwDeviceExtension); ASSERT(NULL != pI2CControl); ASSERT(NULL != pucByte); *pucByte = 0; // // The data bits are read from MSB to LSB. A data bit is read while the SCL is high. // for (lShift = 7; lShift >= 0; lShift--) { pI2CControl->WriteClockLine(pHwDeviceExtension, 1); // Set SCL high I2C_DELAY(pI2CControl); if (I2CWaitForClockLineHigh2(pHwDeviceExtension, pI2CControl) == FALSE) { pVideoDebugPrint((Warn, "VIDEOPRT: I2CReadByte2: SCL didn't take\n")); return FALSE; } *pucByte |= pI2CControl->ReadDataLine(pHwDeviceExtension) << lShift; // Read SDA pI2CControl->WriteClockLine(pHwDeviceExtension, 0); // Set SCL low I2C_DELAY(pI2CControl); } // // Send the acknowledge bit. SDA low = ACK, SDA high = NAK. // if (bEndOfRead) { pI2CControl->WriteDataLine(pHwDeviceExtension, 1); // Set SDA high - NAK } else { pI2CControl->WriteDataLine(pHwDeviceExtension, 0); // Set SDA low - ACK } I2C_DELAY(pI2CControl); // // Send a SCL high->low pulse, then release the SDA by setting it high. // pI2CControl->WriteClockLine(pHwDeviceExtension, 1); // Set SCL high I2C_DELAY(pI2CControl); if (I2CWaitForClockLineHigh2(pHwDeviceExtension, pI2CControl) == FALSE) { pVideoDebugPrint((Warn, "VIDEOPRT: I2CReadByte2: SCL didn't take - ACK failed\n")); return FALSE; } pI2CControl->WriteClockLine(pHwDeviceExtension, 0); // Set SCL low I2C_DELAY(pI2CControl); pI2CControl->WriteDataLine(pHwDeviceExtension, 1); // Set SDA high I2C_DELAY(pI2CControl); return TRUE; } // I2CReadByte2() BOOLEAN I2CWaitForClockLineHigh2( IN PVOID pHwDeviceExtension, IN PVIDEO_I2C_CONTROL pI2CControl ) /*++ Routine Description: This routine waits till SCL goes high (SCL low period can be stretched by slow devices). Arguments: pHwDeviceExtension - Points to per-adapter device extension. pI2CControl - I2C hardware specific functions. Returns: TRUE - OK - SCL high. FALSE - SCL didn't take. --*/ { ULONG ulCount; PAGED_CODE(); ASSERT(NULL != pHwDeviceExtension); ASSERT(NULL != pI2CControl); for (ulCount = 0; ulCount < I2C_SCL_READ_RETRIES; ulCount++) { if (pI2CControl->ReadClockLine(pHwDeviceExtension) == TRUE) return TRUE; I2C_DELAY(pI2CControl); } return FALSE; } // I2CWaitForClockLineHigh2()