/** @file eeprom.c

Access functions to read calibration values of the APCI-3200 eeprom.

@par CREATION
@author Krauth Julien
@date   06.06.05

@par VERSION
@verbatim
$LastChangedRevision$
$LastChangedDate$
@endverbatim

@par COMPILER
@verbatim
Compiler	: gcc
Version	: gcc (GCC) 3.3.6 (Debian 1:3.3.6-5)
kernel	: 2.4.26
@endverbatim

@par UPDATES
@verbatim
Date			: 15.06.2005
Author		: Krauth Julien
Description	: Add timeout management.
Change void apci3200_get_header_calibration_value into<br>
int apci3200_get_header_calibration_value.
@endverbatim

@par LICENCE
@verbatim
Copyright (C) 2004,2005  ADDI-DATA GmbH for the source code of this module.

ADDI-DATA GmbH
Dieselstrasse 3
D-77833 Ottersweier
Tel: +19(0)7223/9493-0
Fax: +49(0)7223/9493-92
http://www.addi-data-com
info@addi-data.com

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

You shoud also find the complete GPL in the COPYING file
accompanying this source code.
@endverbatim
*/

#include "apci3200-private.h"
#include <eeprom.h>

/**@def EXPORT_NO_SYMBOLS
* Function in this file are not exported.
*/
EXPORT_NO_SYMBOLS;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,27)
#define __user
#endif

/** returns header type field or IO error
*
* @retval -1 IO error
* @retval otherwise header type value
*/
static int get_header_type(struct pci_dev *pdev, uint16_t address)
{
	uint16_t headertype;
	if( addi_eeprom_s5920_read (1, GET_BAR0(pdev), address, &headertype) )
		return -1;
	return (headertype & 0x000F); /* type is encoded in last 4 bits */
}

/** check header validity
*
* @retval -1 IO error
* @retval 0 OK
* @retval 0 header is not equal to legal value
*
*/
static int check_header(struct pci_dev *pdev, uint16_t address, uint16_t legal)
{
	int headertype = get_header_type(pdev, address);
	if(headertype==-1)
		return -1;
	return headertype!=legal;
}

/** returns reference voltage field or IO error
*
* @retval -1 IO error
* @retval otherwise field value
*/
static int get_reference_voltage(struct pci_dev *pdev, uint16_t address)
{
	uint16_t ReferenceVoltage;
	if( addi_eeprom_s5920_read (1, GET_BAR0(pdev), address, &ReferenceVoltage) )
		return -1;
	return ReferenceVoltage;
}

/* Fill reference voltage field
* @retval -1 IO error
* @retval otherwise field value
*/
static int get_reference_voltages(struct pci_dev *pdev, uint8_t module,  uint16_t w_SingleHeaderSize, uint16_t w_SingleHeaderAddress, uint16_t w_NumberOfInputs)
{
	unsigned int i;
	for (i = 0; i < w_NumberOfInputs; i++)
	{
		if( -1 == (APCI3200_PRIVDATA(pdev)->s_Module[module].dw_ReferenceVoltage[i] = get_reference_voltage(pdev, ((i*w_SingleHeaderSize) + w_SingleHeaderAddress + 22) ) ) )
			return -1;
	}
	return 0;
}

/** Get the APCI-3200 calibration values from the eeprom.
*
* An eeprom is located on the APCI-3200. In that eeprom are saved some
* calibrations values. These values are used to compute the measured value
* with a higher precision.
*
* @param [in,out]	pdev	Pointer on the device structure.
* @param [in]		index	index of the board (for identification in messages)
*
* @retval	0	No error.
* @retval	-1	Timeout.
* @retval	-2	Incorrect header
*/
int apci3200_get_header_calibration_value (struct pci_dev *pdev, const char* name, int num)
{
	uint16_t w_AnalogInputMainHeaderAddress;
	uint16_t w_AnalogInputComponentAddress;
	uint16_t w_NumberOfModuls = 0;
	uint16_t w_CurrentSources [2];
	uint16_t w_ModulCounter = 0;
	uint16_t w_FirstHeaderSize = 0;
	uint16_t w_NumberOfInputs = 0;
	uint16_t w_CJCFlag = 0;
	uint16_t w_NumberOfGainValue = 0;
	uint16_t w_SingleHeaderAddress = 0;
	uint16_t w_SingleHeaderSize = 0;
	uint16_t w_Input = 0;
	uint16_t w_GainFactorAddress = 0;
	uint16_t w_GainFactorValue [2];
	uint16_t w_GainIndex = 0;
	uint16_t w_GainValue = 0;
	int err = 0;

	/* Get the Analog input header address */
	err = addi_eeprom_s5920_read (1, GET_BAR0(pdev), 0x116, &w_AnalogInputMainHeaderAddress);
	if (err == -1)
		return (-1);

	/* check main header version */
	if( check_header(pdev, w_AnalogInputMainHeaderAddress, 0) )
	{
		printk(KERN_CRIT "%s:%d: incorrect version in main header\n", name, num);
		return -2;
	}
	/* Compute the real analog input address */
	w_AnalogInputMainHeaderAddress = w_AnalogInputMainHeaderAddress + 0x100;

	/* Get the number of moduls */
	err = addi_eeprom_s5920_read (1, GET_BAR0(pdev), w_AnalogInputMainHeaderAddress + 0x02, &w_NumberOfModuls);
	if (err == -1)
		return (-1);

	/* Save the number of modules */
	APCI3200_PRIVDATA(pdev)->w_NbHardwareComponent = w_NumberOfModuls;

	for (w_ModulCounter = 0; w_ModulCounter < w_NumberOfModuls; w_ModulCounter ++)
	{
		/* Compute the component address */
		w_AnalogInputComponentAddress = w_AnalogInputMainHeaderAddress + (w_FirstHeaderSize * w_ModulCounter) + 0x04;

		/* Check component header version */
		if( check_header(pdev, w_AnalogInputComponentAddress, 0) && check_header(pdev, w_AnalogInputComponentAddress, 1) )
		{
			printk(KERN_CRIT "%s:%d: incorrect version in module %d header\n", name, num, w_ModulCounter);
			return -2;
		}
		/* Read first header size */
		if( addi_eeprom_s5920_read (1, GET_BAR0(pdev),
				w_AnalogInputComponentAddress,
				&w_FirstHeaderSize) )
			return -1;

		w_FirstHeaderSize = w_FirstHeaderSize >> 4;

		/* Read number of inputs (actually, analog modules) */
		if( addi_eeprom_s5920_read (1, GET_BAR0(pdev),
				w_AnalogInputComponentAddress + 0x06,
				&w_NumberOfInputs) )
			return -1;

		w_NumberOfInputs = w_NumberOfInputs >> 4;

		/* maximum value is 4 */
		if(w_NumberOfInputs>APCI3200_MAX_ANALOG_INPUTS_PER_MODULE)
		{
			printk(KERN_CRIT "%s:%d:module %d: number of inputs %d is above limit\n", name, num, w_ModulCounter, w_NumberOfInputs);
			return -2;
		}
		/* Read the CJC flag */
		if( addi_eeprom_s5920_read (1, GET_BAR0(pdev),
				w_AnalogInputComponentAddress + 0x08,
				&w_CJCFlag) )
			return -1;

		/* Get only the CJC flag */
		w_CJCFlag = (w_CJCFlag >> 3) & 0x1;

		/* Read number of gain value */
		if( addi_eeprom_s5920_read (1, GET_BAR0(pdev), w_AnalogInputComponentAddress + 0x44, &w_NumberOfGainValue) )
			return -1;

		w_NumberOfGainValue = w_NumberOfGainValue & 0xFF;

		/* Compute single header address */
		w_SingleHeaderAddress = w_AnalogInputComponentAddress + 0x46 + (((w_NumberOfGainValue / 16) + 1) * 2) + (6 * w_NumberOfGainValue) + (4 * (((w_NumberOfGainValue / 16) + 1) * 2));

		/* Check component header version */
		if( check_header(pdev, w_SingleHeaderAddress, 0) )
		{
			printk(KERN_CRIT "%s:%d:module %d: incorrect version in single header\n", name, num, w_ModulCounter);
			return -2;
		}
		/* Read current sources value for input 1 */
		if( addi_eeprom_s5920_read (1, GET_BAR0(pdev), w_SingleHeaderAddress, &w_SingleHeaderSize) )
			return -1;

		w_SingleHeaderSize = w_SingleHeaderSize >> 4;

		/* Read gain factor for the module */
		w_GainFactorAddress = w_AnalogInputComponentAddress;

		for (w_GainIndex = 0; w_GainIndex < w_NumberOfGainValue; w_GainIndex++)
		{
			/* Read gain value for the module */
			if( addi_eeprom_s5920_read (1, GET_BAR0(pdev),
					w_AnalogInputComponentAddress + 70 + (2 * (1 + ( w_NumberOfGainValue / 16))) + (0x02 * w_GainIndex), // Gain value
					&w_GainValue) )
				return -1;

			APCI3200_PRIVDATA(pdev)->s_Module [w_ModulCounter].w_GainValue [w_GainIndex] = w_GainValue;

			/* Read gain factor for the module */
			if( addi_eeprom_s5920_read (2, GET_BAR0(pdev),
					w_AnalogInputComponentAddress + 70 + ((2 * w_NumberOfGainValue) + (2 * (1 + ( w_NumberOfGainValue / 16)))) + (0x04 * w_GainIndex), // Gain factor
					w_GainFactorValue) )
				return -1;

			APCI3200_PRIVDATA(pdev)->s_Module [w_ModulCounter].ul_GainFactor [w_GainIndex] = (w_GainFactorValue [1] << 16) + w_GainFactorValue [0];
		}

		/* Read current source value for each channels of the module */
		for (w_Input = 0; w_Input < w_NumberOfInputs; w_Input++)
		{
			/* Read current sources value for input 1 */
			if( addi_eeprom_s5920_read (2, //i_NbOfWordsToRead
					GET_BAR0(pdev),
					(w_Input * w_SingleHeaderSize) + w_SingleHeaderAddress + 0x0C,
					w_CurrentSources) )
				return -1;

			/* Save the current sources value */
			APCI3200_PRIVDATA(pdev)->s_Module [w_ModulCounter].ul_CurrentSource [w_Input] = (w_CurrentSources [0] + ((w_CurrentSources [1] & 0xFFF) << 16));
		}

		if( get_reference_voltages(pdev, w_ModulCounter, w_SingleHeaderSize, w_SingleHeaderAddress, w_NumberOfInputs) )
			return -1;

		/* Read the CJC current source value */
		if( addi_eeprom_s5920_read (2, //i_NbOfWordsToRead
				GET_BAR0(pdev),
				(w_Input * w_SingleHeaderSize) + w_SingleHeaderAddress + 0x0C,
				w_CurrentSources) )
			return -1;

		/* Save the current sources value */
		APCI3200_PRIVDATA(pdev)->s_Module [w_ModulCounter].ul_CurrentSourceCJC = (w_CurrentSources [0] + ((w_CurrentSources [1] & 0xFFF) << 16));
	}

	return 0;
}
//------------------------------------------------------------------------------

/** Get the APCI-3200 calibration values from the device structure.
*
* @param [in,out]	pdev					Pointer on the device structure.
* @param [int]		channel_num				The channel on which value have to be read.
* @param [int]		ConnectionType			The connection type:<br>
*											- APCI3200_CONNECTION_TYPE_SINGLE<br>
*											- APCI3200_CONNECTION_TYPE_DIFFERENTIAL
* @param [int]		Gain					The agin to use:<br>
*											- APCI3200_GAIN_1<br>
*											- APCI3200_GAIN_2<br>
*											- APCI3200_GAIN_4<br>
*											- APCI3200_GAIN_8<br>
*											- APCI3200_GAIN_16<br>
*											- APCI3200_GAIN_32<br>
*											- APCI3200_GAIN_128
* @param [out]		CJCCurrentSource		The CJC current source value.
* @param [out]		ChannelCurrentSource	The channel current source value.
* @param [out]		ChannelGainFactor		The channel gain factor value.
*
* @retvale			0						No error.
*/
int apci3200_get_channel_calibration_value (struct pci_dev *pdev,
		unsigned int channel_num,
		unsigned int ConnectionType,
		unsigned int Gain,
		unsigned long *CJCCurrentSource,
		unsigned long *ChannelCurrentSource,
		unsigned long *ChannelGainFactor)
{
	int i_DiffChannel = 0;
	int i_Module = 0;

	/* Test if single or differential mode */
	if (ConnectionType == APCI3200_CONNECTION_TYPE_DIFFERENTIAL)
	{
		if((channel_num >=0) && (channel_num <=1))
			i_DiffChannel=channel_num, i_Module = 0;
		else
			if((channel_num >=2) && (channel_num <=3))
				i_DiffChannel=channel_num-2, i_Module = 1;
			else
				if((channel_num >=4) && (channel_num <=5))
					i_DiffChannel=channel_num-4, i_Module = 2;
				else
					if((channel_num >=6) && (channel_num <=7))
						i_DiffChannel=channel_num-6, i_Module = 3;
	}
	else
	{
		if((channel_num == 0) || (channel_num == 1))
			i_DiffChannel=0, i_Module = 0;
		else
			if((channel_num == 2) || (channel_num == 3))
				i_DiffChannel=1, i_Module = 0;
			else
				if((channel_num == 4) || (channel_num == 5))
					i_DiffChannel=0, i_Module = 1;
				else
					if((channel_num == 6) || (channel_num == 7))
						i_DiffChannel=1, i_Module = 1;
					else
						if((channel_num == 8) || (channel_num == 9))
							i_DiffChannel=0, i_Module = 2;
						else
							if((channel_num == 10) || (channel_num == 11))
								i_DiffChannel=1, i_Module = 2;
							else
								if((channel_num == 12) || (channel_num == 13))
									i_DiffChannel=0, i_Module = 3;
								else
									if((channel_num == 14) || (channel_num == 15))
										i_DiffChannel=1, i_Module = 3;
	}

	*CJCCurrentSource = APCI3200_PRIVDATA(pdev)->s_Module[i_Module].ul_CurrentSourceCJC;
	*ChannelCurrentSource = APCI3200_PRIVDATA(pdev)->s_Module[i_Module].ul_CurrentSource [i_DiffChannel];
	*ChannelGainFactor = APCI3200_PRIVDATA(pdev)->s_Module[i_Module].ul_GainFactor [Gain];

	return (0);
}
//------------------------------------------------------------------------------

/** Read the calibration values of the specified analog input channel.
*
* @param [in,out]	pdev	Pointer on the device structure.
* @param [in]		cmd		IOCTL command. The command is <br>
* 							APCI3200_ReadChannelCalibrationValue when using the IOCTL funktion.<br>
* 							To call the function in kernel mode, cmd must be 0.
* @param [in,out]	arg	ReadCalibrationValue_t
*
* @retval 0		No error.
* @retval EFAULT	Fail to get user parameters.
*/
int do_CMD_APCI3200_ReadChannelCalibrationValue(struct pci_dev *pdev, unsigned int cmd, unsigned long arg)
{
	struct ReadCalibrationValue_t CalibrationValue;

	/* Get configuration from user mode */
	if ( copy_from_user( &CalibrationValue, (struct ReadCalibrationValue_t __user *)arg, sizeof(struct ReadCalibrationValue_t) ) )
		return -EFAULT;


	/* Get calibration value */

	{
		unsigned long irqstate;
		APCI3200_LOCK(pdev,&irqstate);
		{
			apci3200_get_channel_calibration_value (pdev,
					(unsigned int) CalibrationValue.b_Channel,
					CalibrationValue.w_ConnectionType,
					CalibrationValue.w_Gain,
					&CalibrationValue.dw_CJCCurrentSource,
					&CalibrationValue.dw_ChannelCurrentSource,
					&CalibrationValue.dw_ChannelGainFactor);
		}
		APCI3200_UNLOCK(pdev,irqstate);
	}
	/* Transfers the value to the user */
	if ( copy_to_user( (struct ReadCalibrationValue_t __user *) arg , &CalibrationValue, sizeof(struct ReadCalibrationValue_t) ) )
		return -EFAULT;

	return 0;
}
