// ADT7473.cpp : Defines the initialization routines for the DLL.
//
// created by Unwinder
/////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"

#include <float.h>
#include <afxdllx.h>
#include <shlwapi.h>

#include "Context.h"
#include "ADT7473.h"
#include "RivaTunerExports.h"
#include "MonitoringSourceDesc.h"
/////////////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
static AFX_EXTENSION_MODULE ADT7473DLL = { NULL, NULL };
/////////////////////////////////////////////////////////////////////////////
#define	MAX_GPU										16
/////////////////////////////////////////////////////////////////////////////
GET_DEVICE_ID_EX_PROC		g_pGetDeviceIDEx		= NULL;
GET_I2C_BUS_NUM_EX_PROC		g_pGetI2CBusNumEx		= NULL;
READ_I2C_EX_PROC			g_pReadI2CEx			= NULL;

CContext					g_context[MAX_GPU];

HINSTANCE					g_hModule				= 0;
/////////////////////////////////////////////////////////////////////////////
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
	UNREFERENCED_PARAMETER(lpReserved);

	if (dwReason == DLL_PROCESS_ATTACH)
	{
		if (!AfxInitExtensionModule(ADT7473DLL, hInstance))
			return 0;

		new CDynLinkLibrary(ADT7473DLL);

		g_hModule = hInstance;
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		AfxTermExtensionModule(ADT7473DLL);
	}
	return 1;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function rips required string param from specified
//<Param0><Separator><Param1><Separator>...<ParamN>-styled string
/////////////////////////////////////////////////////////////////////////////
BOOL GetParam(int iParam, LPCSTR lpSrc, CString &strParam, LPCSTR lpSeparator)
{
	CString strSrc = lpSrc;

	int iFirst = 0;

	while (iParam)
	{
		iFirst = strSrc.Find(lpSeparator, iFirst);

		if (iFirst == -1)
			return FALSE;

		iFirst++;
		iParam--;
	}

	int iLast = strSrc.Find(lpSeparator ,iFirst);

	if (iLast == -1)
		iLast = strlen(lpSrc);

	strParam = strSrc.Mid(iFirst, iLast - iFirst);

	strParam.TrimRight();
	strParam.TrimLeft();

	return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function compares two strings using whildcard character
/////////////////////////////////////////////////////////////////////////////
int CompareWithWildcard(LPCSTR str0, LPCSTR str1, char wildcard)
{
	int nLen0	= strlen(str0);
	int nLen1	= strlen(str1);

	if (nLen0 > nLen1)
		return 1;

	if (nLen0 < nLen1)
		return -1;

	for (int i=0; i<nLen0; i++)
	{
		char c0 = str0[i];
		char c1 = str1[i];

		if ((c0 != wildcard) &&
			(c1 != wildcard))
			if (c0 != c1)
				return (c0 > c1) ? 1 : -1;
	}

	return 0;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function reads the first matching entry for the currently
// selected display adapter from the specified section of .cfg
//
// Note: the entries in .cfg can be specified with wildcard character '?'
/////////////////////////////////////////////////////////////////////////////
CString GetMatchingHardwareEntry(DWORD dwGpu, LPCSTR lpSection)
{
	char szCfgPath[MAX_PATH];
	GetModuleFileName(g_hModule, szCfgPath, MAX_PATH);
	
	PathRenameExtension(szCfgPath, ".cfg");
		//get fully qualified path to .cfg

	LPSTR	lpBuf		= new char[32768];
		//allocate buffer for section
	if (GetPrivateProfileSection(lpSection, lpBuf, 32768, szCfgPath))
		//try to read section, continue if succeeded
	{
		CString strDeviceID	= g_pGetDeviceIDEx(dwGpu);	
			//get display adapter's PCI DeviceID string

		LPSTR	lpStr	= (LPSTR)lpBuf;
 		int		nLen	= strlen(lpStr);

		CString strLValue, strRValue;

		while (nLen)
			//loop through all section's lines
		{

			if (GetParam(0, lpStr, strLValue, "=") &&
				GetParam(1, lpStr, strRValue, "="))
				//try to rip <LValue>=<RValue>-formatted line and proceed
				//if format is correct
			{
				if (!CompareWithWildcard(strLValue, strDeviceID, '?'))
					//compare LValue with PCI DeviceID string
					//take a note that we'll use wildcards to allow multiple
					//display adapters to be specified with a single .cfg line
				{
					delete [] lpBuf;
						//deallocate buffer we've allocated before

					return strRValue;
				}
			}

			lpStr	+=	nLen + 1;
			nLen	=	strlen(lpStr);
				//proceed with next section's line
		}
	}

	delete [] lpBuf;
		//deallocate buffer we've allocated before

	return "";
		//return empty string if we've failed to find matching hardware entry
}
/////////////////////////////////////////////////////////////////////////////
// This helper function reads ADT7473 fan pulses per revolution ratio
/////////////////////////////////////////////////////////////////////////////
int GetPulsesPerRevolution(DWORD dwGpu)
{
	if (g_context[dwGpu].m_nPulsesPerRevolution != -1)
		return g_context[dwGpu].m_nPulsesPerRevolution;

	BYTE data;
	if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x7B, &data))
		g_context[dwGpu].m_nPulsesPerRevolution = (data & 3) + 1;

	return g_context[dwGpu].m_nPulsesPerRevolution;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function reads ADT7473 Vcc
/////////////////////////////////////////////////////////////////////////////
FLOAT GetVcc(DWORD dwGpu)
{
	FLOAT	result	= FLT_MAX;	

	BYTE	data;
	if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x22, &data) && (data != 0xff))
		result = data * 4.4f / 255.0f;

	//perform misreading filtering

	if (result != FLT_MAX)
		g_context[dwGpu].m_fltVcc = result;

	//return last correctly read value

	return g_context[dwGpu].m_fltVcc;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function reads ADT7473 Vccp
/////////////////////////////////////////////////////////////////////////////
FLOAT GetVccp(DWORD dwGpu)
{
	FLOAT	result	= FLT_MAX;	

	BYTE	data;
	if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x21, &data) && (data != 0xff))
		result = data * 3.0f / 255.0f;

	//perform misreading filtering

	if (result != FLT_MAX)
		g_context[dwGpu].m_fltVccp = result;

	//return last correctly read value

	return g_context[dwGpu].m_fltVccp;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function checks if ADT7473 is configured to use twos
// complement temperature format
/////////////////////////////////////////////////////////////////////////////
BOOL IsTwosComplementTemperatureFormat(DWORD dwGpu)
{
	BYTE data;

	if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x7C, &data))
		//read configuration register 5
		return (data & 1); 

	return FALSE;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function reads ADT7473 remote temperature 1
/////////////////////////////////////////////////////////////////////////////
FLOAT GetRemoteTemperature1(DWORD dwGpu)
{
	FLOAT	result	= FLT_MAX;	

	BYTE	data;
	if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x25, &data))
	{
		if (IsTwosComplementTemperatureFormat(dwGpu))
		{
			if (data != 0x80)
				//check diode fault condition
				result = (char)data;
		}
		else
		{
			if (data != 0x00)
				//check diode fault condition
				result = (char)(data - 64);
		}
	}

	//perform misreading filtering

	if (result != FLT_MAX)
		g_context[dwGpu].m_fltTemperatureRemote1 = result;

	//return last correctly read value

	return g_context[dwGpu].m_fltTemperatureRemote1;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function reads ADT7473 remote temperature 2
/////////////////////////////////////////////////////////////////////////////
FLOAT GetRemoteTemperature2(DWORD dwGpu)
{
	FLOAT	result	= FLT_MAX;	

	BYTE	data;
	if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x27, &data))
	{
		if (IsTwosComplementTemperatureFormat(dwGpu))
		{
			if (data != 0x80)
				//check diode fault condition
				result = (char)data;
		}
		else
		{
			if (data)
				//check diode fault condition
				result = (char)(data - 64);
		}
	}

	//perform misreading filtering

	if (result != FLT_MAX)
		g_context[dwGpu].m_fltTemperatureRemote2 = result;

	//return last correctly read value

	return g_context[dwGpu].m_fltTemperatureRemote2;
}
//////////////////////////////////////////////////////////////////////
// This helper function loads settings from .cfg file
//////////////////////////////////////////////////////////////////////
void LoadSettings(DWORD dwGpu)
{
	char szCfgPath[MAX_PATH];
	GetModuleFileName(g_hModule, szCfgPath, MAX_PATH);
	
	PathRenameExtension(szCfgPath, ".cfg");
		//get fully qualified path to .cfg

	sscanf(GetMatchingHardwareEntry(dwGpu, "PulsesPerRevolution"), "%d", &g_context[dwGpu].m_nPulsesPerRevolution);
		//read pulses per revolution number from .cfg
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get a number of
// data sources in this plugin
//////////////////////////////////////////////////////////////////////
ADT7473_API DWORD GetSourcesNum()
{
	return 7;
}
//////////////////////////////////////////////////////////////////////
// This helper function formats multiGPU friendly group name
////////////////////////////////////////////////////////////////////////
CString FormatMultiGpuGroupName(LPCSTR lpName, DWORD dwGpu)
{
	CString strName;

	if (dwGpu)
		strName.Format("%s%d", lpName, dwGpu - 1);
	else
		strName = lpName;

	return strName;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get descriptor
// for the specified data source
//////////////////////////////////////////////////////////////////////
ADT7473_API BOOL GetSourceDesc(DWORD dwIndex, LPMONITORING_SOURCE_DESC pDesc)
{
	//get host application module handle

	HMODULE hHost = GetModuleHandle(NULL);
	if (!hHost)
		return FALSE;

	DWORD dwGpu		= dwIndex>>16;
	DWORD dwSource	= dwIndex & 0xFFFF;
		//extract GPU and data source indices from macro index

	if (dwGpu >= MAX_GPU)
		//validate GPU index
		return FALSE;

	if (!g_context[dwGpu].m_dev)
		//small performance optimization: once the sensor is detected, we'll
		//precache its' location and will no longer perform I2C scanning
	{
		//get ptr to host application ID routine and return error if the host doesn't 
		//export it

		//we'll use this condition to verify if we're under RivaTuner v2.0 RC 15.7
		//(older versions are not supported) or any other compatible host

		GET_HOST_APP_ID_PROC pGetHostAppID = (GET_HOST_APP_ID_PROC)GetProcAddress(hHost, "GetHostAppID");
		if (!pGetHostAppID)
			return FALSE;

		//get ptrs to required plugin API functions

		g_pGetDeviceIDEx	= (GET_DEVICE_ID_EX_PROC)GetProcAddress(hHost, "GetDeviceIDEx");
		if (!g_pGetDeviceIDEx)
			return FALSE;

		g_pGetI2CBusNumEx	= (GET_I2C_BUS_NUM_EX_PROC)GetProcAddress(hHost, "GetI2CBusNumEx");
		if (!g_pGetI2CBusNumEx)
			return FALSE;

		g_pReadI2CEx		= (READ_I2C_EX_PROC)GetProcAddress(hHost, "ReadI2CEx");
		if (!g_pReadI2CEx)
			return FALSE;

		//once we've got all function ptrs, we can proceed with I2C bus scanning

		//first, we'll get number of I2C buses for the selected display device

		BYTE busNum		= g_pGetI2CBusNumEx(dwGpu);
		if (!busNum)
			return FALSE;

		//scan all I2C buses and check ADT7473 sensor presence	

		for (BYTE bus=0; bus<busNum; bus++)
		{
			BYTE data;

			if (g_pReadI2CEx(dwGpu, bus, 0x2E, 0x3D, &data) && (data == 0x73))
				//check ADT7473 manufacturer ID
				if (g_pReadI2CEx(dwGpu, bus, 0x2E, 0x3E, &data) && (data == 0x41))
					//check ADT7473 revision ID
				{
					//we've found ADT7473 and can precache its' location

					g_context[dwGpu].m_bus = bus;
					g_context[dwGpu].m_dev = 0x2E;
					break;
				}
		}

		//load settings from .cfg

		LoadSettings(dwGpu);
	}

	if (!g_context[dwGpu].m_dev)
		//return FALSE if we've not detected ADT7473
		return FALSE;

	CString strSourceName;

	switch (dwSource)
		//now the sensor's location is detected,
		//so we can return requested information to RivaTuner
	{
	case 0:
		//fill remote temperature 1 source descriptor

		strSourceName = GetMatchingHardwareEntry(dwGpu, "TemperatureName1");
		if (strSourceName.IsEmpty())
			return FALSE;

		if (GetRemoteTemperature1(dwGpu) == FLT_MAX)
			//check if remote temperature channel 1 is in use
			return FALSE;

		strcpy(pDesc->szName	, strSourceName);
		strcpy(pDesc->szDim		, "°C");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, FormatMultiGpuGroupName(GetMatchingHardwareEntry(dwGpu, "TemperatureGroup1"), dwGpu));

		pDesc->fltMaxLimit	= 200.0f;
		pDesc->fltMinLimit	= 0.0f;
		pDesc->fltGridDim	= 10.0f;

		strcpy(pDesc->szDesc, " Remote temperature 1 reported by ADT7473-compatible sensor ");

		return TRUE;

	case 1:
		//fill ambient temperature source descriptor

		strcpy(pDesc->szName	, "Ambient temperature");
		strcpy(pDesc->szDim		, "°C");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, FormatMultiGpuGroupName("PCB", dwGpu));

		pDesc->fltMaxLimit	= 200.0f;
		pDesc->fltMinLimit	= 0.0f;
		pDesc->fltGridDim	= 10.0f;

		strcpy(pDesc->szDesc, " Local temperature reported by ADT7473-compatible sensor ");

		return TRUE;

	case 2:
		//fill fan speed source descriptor

		strcpy(pDesc->szName	, "Fan speed");
		strcpy(pDesc->szDim		, "RPM");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, FormatMultiGpuGroupName("GPU", dwGpu));

		pDesc->fltMaxLimit	= 10000.0f;
		pDesc->fltMinLimit	= 0.0f;
		pDesc->fltGridDim	= 10.0f;

		strcpy(pDesc->szDesc, " Fan speed reported by ADT7473-compatible sensor ");

		return TRUE;

	case 3:
		//fill fan duty cycle source descriptor

		strcpy(pDesc->szName	, "Fan duty cycle");
		strcpy(pDesc->szDim		, "%");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, FormatMultiGpuGroupName("GPU", dwGpu));

		pDesc->fltMaxLimit	= 100.0f;
		pDesc->fltMinLimit	= 0.0f;
		pDesc->fltGridDim	= 10.0f;

		strcpy(pDesc->szDesc, " Fan duty cycle reported by ADT7473-compatible sensor ");

		return TRUE;

	case 4:
		//fill remote temperature 2 source descriptor

		strSourceName = GetMatchingHardwareEntry(dwGpu, "TemperatureName2");
		if (strSourceName.IsEmpty())
			return FALSE;

		if (GetRemoteTemperature2(dwGpu) == FLT_MAX)
			//check if remote temperature channel 2 is in use
			return FALSE;

		strcpy(pDesc->szName	, strSourceName);
		strcpy(pDesc->szDim		, "°C");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, FormatMultiGpuGroupName(GetMatchingHardwareEntry(dwGpu, "TemperatureGroup2"), dwGpu));

		pDesc->fltMaxLimit	= 200.0f;
		pDesc->fltMinLimit	= 0.0f;
		pDesc->fltGridDim	= 10.0f;

		strcpy(pDesc->szDesc, " Remote temperature 2 reported by ADT7473-compatible sensor ");

		return TRUE;

	case 5:
		//fill Vcc source descriptor

		strSourceName = GetMatchingHardwareEntry(dwGpu, "VccName");
		if (strSourceName.IsEmpty())
			return FALSE;

		if (GetVcc(dwGpu) == FLT_MAX)
			//check if Vcc pin is in use
			return FALSE;

		strcpy(pDesc->szName	, strSourceName);
		strcpy(pDesc->szDim		, "V");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, FormatMultiGpuGroupName(GetMatchingHardwareEntry(dwGpu, "VccGroup"), dwGpu));

		pDesc->fltMaxLimit	= 5.0f;
		pDesc->fltMinLimit	= 0.0f;
		pDesc->fltGridDim	= 10.0f;

		strcpy(pDesc->szDesc, " Vcc voltage reported by ADT7473-compatible sensor ");

		return TRUE;

	case 6:
		//fill Vccp source descriptor

		strSourceName = GetMatchingHardwareEntry(dwGpu, "VccpName");
		if (strSourceName.IsEmpty())
			return FALSE;

		if (GetVccp(dwGpu) == FLT_MAX)
			//check if Vccp pin is in use
			return FALSE;

		strcpy(pDesc->szName	, strSourceName);
		strcpy(pDesc->szDim		, "V");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, FormatMultiGpuGroupName(GetMatchingHardwareEntry(dwGpu, "VccpGroup"), dwGpu));

		pDesc->fltMaxLimit	= 3.0f;
		pDesc->fltMinLimit	= 0.0f;
		pDesc->fltGridDim	= 10.0f;

		strcpy(pDesc->szDesc, " Vccp voltage reported by ADT7473-compatible sensor ");

		return TRUE;
	}

	return FALSE;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get capabilities
// for the specified data source
//////////////////////////////////////////////////////////////////////
ADT7473_API BOOL GetSourceCaps(DWORD dwIndex, DWORD dwCaps, LPDWORD lpResult)
{
	//all sources support multiGPU monitoring so it is safe to ignore passed
	//index and check just the capability

	switch (dwCaps)
	{
	case  MONITORING_SOURCE_CAPS_MULTIGPU_SUPPORT:
		*lpResult = 1;
		return TRUE;
	}

	return FALSE;
}
/////////////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to poll data sources
/////////////////////////////////////////////////////////////////////////////
ADT7473_API FLOAT GetSourceData(DWORD dwIndex)
{                                 	
	DWORD dwGpu		= dwIndex>>16;
	DWORD dwSource	= dwIndex & 0xFFFF;
		//extract GPU and data source indices from macro index

	if (dwGpu >= MAX_GPU)
		//validate GPU index
		return FLT_MAX;

	FLOAT	result	= FLT_MAX;	
	WORD	tach;
	BYTE	data;

	switch (dwSource)
	{
	case 0:
		//remote temperature 1

		return GetRemoteTemperature1(dwGpu);

	case 1:
		//ambient temperature

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x26, &data))
		{
			if (IsTwosComplementTemperatureFormat(dwGpu))
				result = (char)data;
			else
				result = (char)(data - 64);
		}

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltTemperatureLocal = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltTemperatureLocal;

	case 2:
		//fan speed

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x28, &data))
		{
			tach = data;

			if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x29, &data))
			{
				tach |= data<<8;

				if (tach)
				{
					if (tach == 0xffff)
						//handle fan stop case
						result = 0.0f;
					else
					{
						int nPulsesPerRevolution = GetPulsesPerRevolution(dwGpu);

						if (nPulsesPerRevolution != -1)
							result = 90000.0f * 30.0f * nPulsesPerRevolution / tach;
					}
				}

			}
		}

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltFanSpeed = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltFanSpeed;

	case 3:
		//fan duty cycle

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x30, &data))
			result = 100.0f * data / 255.0f;

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltFanDutyCycle = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltFanDutyCycle;

	case 4:
		//remote temperature 2

		return GetRemoteTemperature2(dwGpu);

	case 5:
		//Vcc

		return GetVcc(dwGpu);

	case 6:
		//Vccp

		return GetVccp(dwGpu);
	}

	return result;
}
/////////////////////////////////////////////////////////////////////////////
