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

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

#include "Context.h"
#include "LM63.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 LM63DLL = { 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(LM63DLL, hInstance))
			return 0;

		new CDynLinkLibrary(LM63DLL);


		g_hModule = hInstance;
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		AfxTermExtensionModule(LM63DLL);
	}
	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 returns TRUE if the specified data should be treated
// as misread
//
// Background:
//
//Due to unknown reason (presumably hardware bug of R360's I2C controller)
//it can return errorneous zero data on successful I2C read transactions.
//We'll filter such values on these boards 
/////////////////////////////////////////////////////////////////////////////
BOOL IsMisreadData(DWORD dwGpu, BYTE data)
{
	return g_context[dwGpu].m_bMisreadingFilter && (data == 0);
}
/////////////////////////////////////////////////////////////////////////////
// This helper function is used to get fan speed or FLT_MAX if there is no
// fan connected to tachometer's input
//
// Note: 2 pulse per revolution tachometer is assumed in calcualtions
/////////////////////////////////////////////////////////////////////////////
FLOAT GetFanSpeed(DWORD dwGpu)
{
	FLOAT	result = FLT_MAX;

	WORD	tach;
	BYTE	data;

	if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x46, &data) && !IsMisreadData(dwGpu, data))
		//read LSB
	{
		tach = data;

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x47, &data) && !IsMisreadData(dwGpu, data))
			//read MSB and combine it with LSB
		{
			tach |= data<<8;

			if (tach)
				result = 5400000.0f / tach;

			if (tach == 0xffff)
				//handle fan stop case
				result = 0.0f;
		}
	}

	//perform misreading filtering

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


	//return last correctly read value

	return g_context[dwGpu].m_fltFanSpeed;
}
/////////////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get a number of
// data sources in this plugin
/////////////////////////////////////////////////////////////////////////////
LM63_API DWORD GetSourcesNum()
{
	return 4;
}
//////////////////////////////////////////////////////////////////////
// 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
/////////////////////////////////////////////////////////////////////////////
LM63_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 LM63-compatible sensor presence	

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

			if (g_pReadI2CEx(dwGpu, bus, 0x18, 0xFE, &data) && (data == 0x01))
					//check LM64 manufacturer ID
			{
				if (g_pReadI2CEx(dwGpu, bus, 0x18, 0xFF, &data) && (data == 0x51))
					//check LM64 revision ID
				{
					//we've found LM64 and can precache its' location

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

			if (g_pReadI2CEx(dwGpu, bus, 0x4E, 0xFE, &data) && (data == 0x01))
					//check LM64 manufacturer ID
			{
				if (g_pReadI2CEx(dwGpu, bus, 0x4E, 0xFF, &data) && (data == 0x51))
					//check LM64 revision ID
				{
					//we've found LM64 and can precache its' location

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

			if (g_pReadI2CEx(dwGpu, bus, 0x4C, 0xFE, &data)) 
				if (data == 0x01)
					//check LM63 manufacturer ID
				{
					if (g_pReadI2CEx(dwGpu, bus, 0x4C, 0xFF, &data) && (data == 0x41))
						//check LM63/LM64 revision ID
					{
						//we've found LM63 and can precache its' location

						g_context[dwGpu].m_bus = bus;
						g_context[dwGpu].m_dev = 0x4C;
						break;
					}
				}
				else
					if (data == 0x23)
						//check F75363S manufacturer ID
					{
						if (g_pReadI2CEx(dwGpu, bus, 0x4C, 0xFF, &data) && ((data == 0x11) || (data == 0x20)))
							//check F75363S revision ID (both retail and engineering revisions)
						{
							//we've found F75363S and can precache its' location

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

		//read PWM polarity from the configuration file
		sscanf(GetMatchingHardwareEntry(dwGpu, "PWMPolarity"), "%d", &g_context[dwGpu].m_bPWMPolarity);
	}

	if (!g_context[dwGpu].m_dev)
		//return FALSE if we've not detected LM63-compatible sensor
		return FALSE;

	//now we've to setup additional misreading filter for 9800XT series

	//due to unknown reason (presumably hardware bug of R360's I2C controller)
	//it can return errorneous zero data on successful I2C read transactions.

	//we'll filter such values on these boards

	CString strDeviceID = g_pGetDeviceIDEx(dwGpu);	strDeviceID.MakeUpper();

	DWORD	vendorID, deviceID;
	DWORD	subsysID;
	DWORD	revision;

	sscanf(strDeviceID,"PCI\\VEN_%04x&DEV_%04x&SUBSYS_%08x&REV_%02x", &vendorID, &deviceID, &subsysID, &revision);

	if ((vendorID == 0x1002) && ((deviceID & 0xfff0) == 0x4E40))
		//it is R350/R360, enable misreading filtering
		g_context[dwGpu].m_bMisreadingFilter = TRUE;

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

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

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

		strcpy(pDesc->szDesc, " Remote temperature reported by LM63-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 LM63-compatible sensor ");

		return TRUE;

	case 2:
		//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 LM63-compatible sensor ");

		return TRUE;

	case 3:
		//check if fan is connected to tachometer and fill fan speed source
		//descriptor if necessary

		if (GetFanSpeed(dwGpu) == FLT_MAX)
			return FALSE;

		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 LM63-compatible sensor ");

		return TRUE;
	}

	return FALSE;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get capabilities
// for the specified data source
//////////////////////////////////////////////////////////////////////
LM63_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
/////////////////////////////////////////////////////////////////////////////
LM63_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;	
	SHORT	temp;
	BYTE	data;

	switch (dwSource)
	{
	case 0:
		//core temperature

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 1, &data) && !IsMisreadData(dwGpu, data))
			//read MSB
		{
			temp = data<<8;

			if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x10, &data) && !IsMisreadData(dwGpu, data))
				//read LSB and combine it with MSB
				temp |= data;

			result = temp / 256.0f;
		}

		//perform misreading filtering

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

		//return last correctly read value

		return g_context[dwGpu].m_fltTemperatureRemote;

	case 1:
		//ambient temperature

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0, &data) && !IsMisreadData(dwGpu, data))
			//read MSB
		{
			temp = data<<8;

			if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x11, &data) && !IsMisreadData(dwGpu, data))
				//read LSB and combine it with MSB
				temp |= data;

			result = temp / 256.0f;
		}

		//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 duty cycle

		{
			if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x4D, &data) && !IsMisreadData(dwGpu, data))
				//read PWM frequency
			{
				FLOAT freq = (FLOAT)(data & 0x1f);

				if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x4C, &data) && !IsMisreadData(dwGpu, data))
					//read PWM value and calculate duty cycle
				{

					FLOAT val = (FLOAT)(data & 0x3f);
			
					result = val * 100.0f / (2.0f * freq);

					if (result > 100.0f)
						result = 100.0f;

					if (!g_context[dwGpu].m_bPWMPolarity)
						result = 100.0f - result;
				}

			}
		}

		//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 3:
		//fan speed

		return GetFanSpeed(dwGpu);

	}

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