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

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

#include "Context.h"
#include "MAX6648.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 MAX6648DLL = { NULL, NULL };
/////////////////////////////////////////////////////////////////////////////
#define MAX_GPU															16
/////////////////////////////////////////////////////////////////////////////
GET_I2C_BUS_NUM_EX_PROC		g_pGetI2CBusNumEx		= NULL;
READ_I2C_EX_PROC			g_pReadI2CEx			= NULL;

CContext					g_context[MAX_GPU];
/////////////////////////////////////////////////////////////////////////////
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
	UNREFERENCED_PARAMETER(lpReserved);

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

		new CDynLinkLibrary(MAX6648DLL);
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		AfxTermExtensionModule(MAX6648DLL);
	}
	return 1;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get a number of
// data sources in this plugin
//////////////////////////////////////////////////////////////////////
MAX6648_API DWORD GetSourcesNum()
{
	return 2;
}
//////////////////////////////////////////////////////////////////////
// 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
//////////////////////////////////////////////////////////////////////
MAX6648_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_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 MAX6648 sensor presence	

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

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

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

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

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

		return TRUE;
	}

	return FALSE;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get capabilities
// for the specified data source
//////////////////////////////////////////////////////////////////////
MAX6648_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
/////////////////////////////////////////////////////////////////////////////
MAX6648_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))
			//read MSB
		{
			temp = data<<8;

			if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x10, &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))
			//read MSB
		{
			temp = data<<8;

			if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x11, &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;
	}

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