windows-nt/Source/XPSP1/NT/drivers/video/ms/port/i2c.c
2020-09-26 16:20:57 +08:00

514 lines
12 KiB
C

/*++
Copyright (c) 1990-2000 Microsoft Corporation
Module Name:
i2c.c
Abstract:
This is the NT Video port I2C helper code.
Author:
Michael Maciesowicz (mmacie) 03-Sept-1999
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() DELAY_MICROSECONDS(5)
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, I2CStart)
#pragma alloc_text (PAGE, I2CStop)
#pragma alloc_text (PAGE, I2CWrite)
#pragma alloc_text (PAGE, I2CRead)
#pragma alloc_text (PAGE, I2CWriteByte)
#pragma alloc_text (PAGE, I2CReadByte)
#pragma alloc_text (PAGE, I2CWaitForClockLineHigh)
#endif // ALLOC_PRAGMA
//
// Routines exported via VideoPortQueryServices().
//
BOOLEAN
I2CStart(
IN PVOID pHwDeviceExtension,
IN PI2C_CALLBACKS pI2CCallbacks
)
/*++
Routine Description:
This routine starts I2C communication.
Arguments:
pHwDeviceExtension - Points to per-adapter device extension.
pI2CCallbacks - I2C hardware specific functions.
Returns:
TRUE - Start OK.
FALSE - Start failed.
--*/
{
ULONG ulRetry;
PAGED_CODE();
ASSERT(NULL != pHwDeviceExtension);
ASSERT(NULL != pI2CCallbacks);
ASSERT(NULL != pI2CCallbacks->WriteClockLine);
ASSERT(NULL != pI2CCallbacks->WriteDataLine);
ASSERT(NULL != pI2CCallbacks->ReadClockLine);
ASSERT(NULL != pI2CCallbacks->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++)
{
pI2CCallbacks->WriteDataLine(pHwDeviceExtension, 1); // Set SDA high
I2C_DELAY();
if (pI2CCallbacks->ReadDataLine(pHwDeviceExtension) == FALSE) // SDA didn't take - ulRetry
continue;
pI2CCallbacks->WriteClockLine(pHwDeviceExtension, 1); // Set SCL high
I2C_DELAY();
if (I2CWaitForClockLineHigh(pHwDeviceExtension, pI2CCallbacks) == FALSE)
{
pVideoDebugPrint((Warn, "VIDEOPRT: I2CStart: SCL didn't take\n"));
break;
}
pI2CCallbacks->WriteDataLine(pHwDeviceExtension, 0); // Set SDA low
I2C_DELAY();
pI2CCallbacks->WriteClockLine(pHwDeviceExtension, 0); // Set SCL low
I2C_DELAY();
return TRUE;
}
pVideoDebugPrint((Warn, "VIDEOPRT: I2CStart: Failed\n"));
return FALSE;
} // I2CStart()
BOOLEAN
I2CStop(
IN PVOID pHwDeviceExtension,
IN PI2C_CALLBACKS pI2CCallbacks
)
/*++
Routine Description:
This routine stops I2C communication.
Arguments:
pHwDeviceExtension - Points to per-adapter device extension.
pI2CCallbacks - I2C hardware specific functions.
Returns:
TRUE - Stop OK.
FALSE - Stop failed.
--*/
{
PAGED_CODE();
ASSERT(NULL != pHwDeviceExtension);
ASSERT(NULL != pI2CCallbacks);
ASSERT(NULL != pI2CCallbacks->WriteClockLine);
ASSERT(NULL != pI2CCallbacks->WriteDataLine);
ASSERT(NULL != pI2CCallbacks->ReadClockLine);
ASSERT(NULL != pI2CCallbacks->ReadDataLine);
ASSERT(IS_HW_DEVICE_EXTENSION(pHwDeviceExtension) == TRUE);
//
// The I2C communications stop signal is a SDA low->high while the SCL is high.
//
pI2CCallbacks->WriteDataLine(pHwDeviceExtension, 0); // Set SDA low
I2C_DELAY();
pI2CCallbacks->WriteClockLine(pHwDeviceExtension, 1); // Set SCL high
I2C_DELAY();
if (I2CWaitForClockLineHigh(pHwDeviceExtension, pI2CCallbacks) == FALSE)
{
pVideoDebugPrint((Warn, "VIDEOPRT: I2CStop: SCL didn't take\n"));
return FALSE;
}
pI2CCallbacks->WriteDataLine(pHwDeviceExtension, 1); // Set SDA high
I2C_DELAY();
if (pI2CCallbacks->ReadDataLine(pHwDeviceExtension) != 1)
{
pVideoDebugPrint((Warn, "VIDEOPRT: I2CStop: SDA didn't take\n"));
return FALSE;
}
return TRUE;
} // I2CStop()
BOOLEAN
I2CWrite(
IN PVOID pHwDeviceExtension,
IN PI2C_CALLBACKS pI2CCallbacks,
IN PUCHAR pucBuffer,
IN ULONG ulLength
)
/*++
Routine Description:
This routine writes data over the I2C channel.
Arguments:
pHwDeviceExtension - Points to per-adapter device extension.
pI2CCallbacks - 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 != pI2CCallbacks);
ASSERT(NULL != pucBuffer);
ASSERT(NULL != pI2CCallbacks->WriteClockLine);
ASSERT(NULL != pI2CCallbacks->WriteDataLine);
ASSERT(NULL != pI2CCallbacks->ReadClockLine);
ASSERT(NULL != pI2CCallbacks->ReadDataLine);
ASSERT(IS_HW_DEVICE_EXTENSION(pHwDeviceExtension) == TRUE);
for (ulCount = 0; ulCount < ulLength; ulCount++)
{
if (I2CWriteByte(pHwDeviceExtension, pI2CCallbacks, pucBuffer[ulCount]) == FALSE)
{
return FALSE;
}
}
return TRUE;
} // I2CWrite()
BOOLEAN
I2CRead(
IN PVOID pHwDeviceExtension,
IN PI2C_CALLBACKS pI2CCallbacks,
OUT PUCHAR pucBuffer,
IN ULONG ulLength
)
/*++
Routine Description:
This routine reads data over the I2C channel.
Arguments:
pHwDeviceExtension - Points to per-adapter device extension.
pI2CCallbacks - I2C hardware specific functions.
pucBuffer - Points to storage for data.
ulLength - Number of bytes to read.
Returns:
TRUE - Read OK.
FALSE - Read failed.
--*/
{
ULONG ulCount;
PAGED_CODE();
ASSERT(NULL != pHwDeviceExtension);
ASSERT(NULL != pI2CCallbacks);
ASSERT(NULL != pucBuffer);
ASSERT(NULL != pI2CCallbacks->WriteClockLine);
ASSERT(NULL != pI2CCallbacks->WriteDataLine);
ASSERT(NULL != pI2CCallbacks->ReadClockLine);
ASSERT(NULL != pI2CCallbacks->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)
{
if (I2CReadByte(pHwDeviceExtension, pI2CCallbacks, pucBuffer + ulCount, FALSE) == FALSE) // Last byte
{
return FALSE;
}
}
else
{
if (I2CReadByte(pHwDeviceExtension, pI2CCallbacks, pucBuffer + ulCount, TRUE) == FALSE)
{
return FALSE;
}
}
}
return TRUE;
} // I2CRead()
//
// Local routines.
//
BOOLEAN
I2CWriteByte(
IN PVOID pHwDeviceExtension,
IN PI2C_CALLBACKS pI2CCallbacks,
IN UCHAR ucByte
)
/*++
Routine Description:
This routine writes byte over the I2C channel.
Arguments:
pHwDeviceExtension - Points to per-adapter device extension.
pI2CCallbacks - 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 != pI2CCallbacks);
//
// Bits are transmitted serially starting with the MSB.
//
for (lShift = 7; lShift >= 0; lShift--)
{
//
// Transmitt data bit.
//
pI2CCallbacks->WriteDataLine(pHwDeviceExtension, (UCHAR)((ucByte >> lShift) & 0x01)); // Set SDA
I2C_DELAY();
//
// After each data bit we must send high->low SCL pulse.
//
pI2CCallbacks->WriteClockLine(pHwDeviceExtension, 1); // Set SCL high
I2C_DELAY();
if (I2CWaitForClockLineHigh(pHwDeviceExtension, pI2CCallbacks) == FALSE)
{
pVideoDebugPrint((Warn, "VIDEOPRT: I2CWriteByte: SCL didn't take\n"));
return FALSE;
}
pI2CCallbacks->WriteClockLine(pHwDeviceExtension, 0); // Set SCL low
I2C_DELAY();
}
//
// 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.
//
pI2CCallbacks->WriteDataLine(pHwDeviceExtension, 1); // Set SDA high
I2C_DELAY();
pI2CCallbacks->WriteClockLine(pHwDeviceExtension, 1); // Set SCL high
I2C_DELAY();
if (I2CWaitForClockLineHigh(pHwDeviceExtension, pI2CCallbacks) == FALSE)
{
pVideoDebugPrint((Warn, "VIDEOPRT: I2CWriteByte: SCL didn't take - ACK failed\n"));
return FALSE;
}
ucAck = pI2CCallbacks->ReadDataLine(pHwDeviceExtension); // Read ACK bit
pI2CCallbacks->WriteClockLine(pHwDeviceExtension, 0); // Set SCL low
I2C_DELAY();
if (1 == ucAck) // NAK from the monitor
{
pVideoDebugPrint((Warn, "VIDEOPRT: I2CWriteByte: NAK received\n"));
return FALSE;
}
return TRUE;
} // I2CWriteByte()
BOOLEAN
I2CReadByte(
IN PVOID pHwDeviceExtension,
IN PI2C_CALLBACKS pI2CCallbacks,
OUT PUCHAR pucByte,
IN BOOLEAN bMore
)
/*++
Routine Description:
This routine reads byte over the I2C channel.
Arguments:
pHwDeviceExtension - Points to per-adapter device extension.
pI2CCallbacks - I2C hardware specific functions.
pucBuffer - Points to storage for data.
bMore - TRUE if we want to continue reading, FALSE otherwise.
Returns:
TRUE - Read OK.
FALSE - Read failed.
--*/
{
LONG lShift;
PAGED_CODE();
ASSERT(NULL != pHwDeviceExtension);
ASSERT(NULL != pI2CCallbacks);
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--)
{
pI2CCallbacks->WriteClockLine(pHwDeviceExtension, 1); // Set SCL high
I2C_DELAY();
if (I2CWaitForClockLineHigh(pHwDeviceExtension, pI2CCallbacks) == FALSE)
{
pVideoDebugPrint((Warn, "VIDEOPRT: I2CReadByte: SCL didn't take\n"));
return FALSE;
}
*pucByte |= pI2CCallbacks->ReadDataLine(pHwDeviceExtension) << lShift; // Read SDA
pI2CCallbacks->WriteClockLine(pHwDeviceExtension, 0); // Set SCL low
I2C_DELAY();
}
//
// Send the acknowledge bit. SDA low = ACK, SDA high = NAK.
//
if (TRUE == bMore)
{
pI2CCallbacks->WriteDataLine(pHwDeviceExtension, 0); // Set SDA low - ACK
}
else
{
pI2CCallbacks->WriteDataLine(pHwDeviceExtension, 1); // Set SDA high - NAK
}
I2C_DELAY();
//
// Send a SCL high->low pulse, then release the SDA by setting it high.
//
pI2CCallbacks->WriteClockLine(pHwDeviceExtension, 1); // Set SCL high
I2C_DELAY();
if (I2CWaitForClockLineHigh(pHwDeviceExtension, pI2CCallbacks) == FALSE)
{
pVideoDebugPrint((Warn, "VIDEOPRT: I2CReadByte: SCL didn't take - ACK failed\n"));
return FALSE;
}
pI2CCallbacks->WriteClockLine(pHwDeviceExtension, 0); // Set SCL low
I2C_DELAY();
pI2CCallbacks->WriteDataLine(pHwDeviceExtension, 1); // Set SDA high
I2C_DELAY();
return TRUE;
} // I2CReadByte()
BOOLEAN
I2CWaitForClockLineHigh(
IN PVOID pHwDeviceExtension,
IN PI2C_CALLBACKS pI2CCallbacks
)
/*++
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.
pI2CCallbacks - I2C hardware specific functions.
Returns:
TRUE - OK - SCL high.
FALSE - SCL didn't take.
--*/
{
ULONG ulCount;
PAGED_CODE();
ASSERT(NULL != pHwDeviceExtension);
ASSERT(NULL != pI2CCallbacks);
for (ulCount = 0; ulCount < I2C_SCL_READ_RETRIES; ulCount++)
{
if (pI2CCallbacks->ReadClockLine(pHwDeviceExtension) == TRUE)
return TRUE;
I2C_DELAY();
}
return FALSE;
} // I2CWaitForClockLineHigh()