516 lines
13 KiB
C
516 lines
13 KiB
C
|
/*++
|
||
|
|
||
|
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()
|