/** @file ana_inputs.c

Contains analog inputs functions, to initialize and read analog values on the APCI-3200.

@par CREATION
@author Krauth Julien
@date   06.06.05

@par VERSION
@verbatim
$LastChangedRevision: 808 $
$LastChangedDate: 2008-10-17 11:04:18 +0200 (Fri, 17 Oct 2008) $
@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			: 18.07.2005
Author		: Krauth Julien
Description	: Correction on channel 1,2,3 the channel number was not updated.

@par UPDATES
@verbatim
Date			: 15.07.2005
Author		: Krauth Julien
Description	: Correction that permit to get value from all 16 thermocouple channels.

Date			: 15.06.2005
Author		: Krauth Julien
Description	: Add timeout management in OUTPDW_WAIT_READY and<br>
a new function test_eoc_with_timeout to test the eoc flag<br>
with timeout.
@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 "iomap.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

//#define MARK() printk("%s:%d\n",__FUNCTION__,__LINE__)
#define MARK()


/** Initialize the analog input. */
int i_APCI3200_InitAnalogInputChannel(struct pci_dev *pdev, struct InitAnalogInputChannel_t *AnalogInputConfig)
{
	register unsigned long module_address = module_to_address(pdev,channel_to_module(AnalogInputConfig->b_Channel));

	/* Write the channel to configure */
	if( outl_module(module_address, APCI3200_REG_ACQUISITION_CONFIG, channel_to_index(AnalogInputConfig->b_Channel) ) )
		return -EIO;

	/* Reset the configuration */
	if( outl_module(module_address, APCI3200_REG_CHANNEL_CONFIG, 0) )
		return -EIO;

	/* Prepare the configuration to write */
	{
		unsigned long dw_Configuration = 0;
		dw_Configuration = AnalogInputConfig->w_GainSelected | ( (AnalogInputConfig->b_PolaritySelected == APCI3200_POLARITY_UNIPOLAR?1:0) << 6) | ( AnalogInputConfig->b_DiffSingSelected << 7);

		/* Write the configuration */
		if( outl_module(module_address, APCI3200_REG_CHANNEL_CONFIG, dw_Configuration) )
			return -EIO;
	}

	/* Reset the calibration bits */
	{
		uint32_t dw_Value = INPDW (module_address + APCI3200_REG_AUTOCALIBRATION);
		if( outl_module(module_address, APCI3200_REG_AUTOCALIBRATION, (dw_Value & 0xFFF9FFFF)) )
			return -EIO;
	}
	return (0);
}

/** Set / Reset the diagnostic mode.
 *
 * This function permits to enable disable diagnostic test.
 *
 * @param [in,out]	pdev	Pointer on the device structure.
 *
 * @param [in]		mode:
 *  					- APCI3200_DIAGNOSTIC_NONE:				Disable diagnostic test.
 * 						- APCI3200_DIAGNOSTIC_OPEN_CONNECTION:	Open connection test.
 * @param [in]		channel: The channel to test (single 0 - 15, differential 0 - 7).
 *
 * @retval 0		No error.
 * @retval -EINVAL	Incorrect parameter.
 */
int i_APCI3200_SetDiagnosticMode (struct pci_dev *pdev, unsigned char mode, unsigned char channel)
{
	if ((mode != APCI3200_DIAGNOSTIC_NONE) && (mode != APCI3200_DIAGNOSTIC_OPEN_CONNECTION))
		return -EINVAL;

	if (channel > 15)
		return -EINVAL;

	APCI3200_PRIVDATA(pdev)->DiagnosticMode[channel] = mode;

	return 0;
}

/** Get the diagnostic mode.
 *
 * This function permits to get the configured diagnostic test.
 *
 * @param [in,out]	pdev	Pointer on the device structure.
 * @param [in]		channel: The channel to test (single 0 - 15, differential 0 - 7).
 *
 * @retval APCI3200_DIAGNOSTIC_NONE:				Disable diagnostic test.
 * @retval APCI3200_DIAGNOSTIC_OPEN_CONNECTION:		Open connection test.
 * @retval -EINVAL	Incorrect parameter.
 * @retval -EFAULT	Fail to return user parameters.
 */
int i_APCI3200_GetDiagnosticMode (struct pci_dev *pdev, unsigned char channel)
{
	if (channel > 15)
		return -EINVAL;

	return ((int)(APCI3200_PRIVDATA(pdev)->DiagnosticMode[channel]));
}

int i_APCI3200_Read1AnalogInputChannelFast (struct pci_dev *pdev, unsigned char channel, unsigned long *value)
{
	int module = channel_to_module(channel);
	register unsigned long module_address = module_to_address(pdev,module);
	register uint32_t config = channel_to_index(channel);

	/* Test if diagnostic mode is to be used */
	if (i_APCI3200_GetDiagnosticMode (pdev, channel) == APCI3200_DIAGNOSTIC_OPEN_CONNECTION)
	{
		config = (config | 0x00000800);
	}

	/* Write the channel to configure */
	if( outl_module(module_address, APCI3200_REG_ACQUISITION_CONFIG, config ) )
		return -EIO;

	/* Start the acquisition */
	if( outl_module(module_address, APCI3200_REG_ACQUISITION_CONTROL, 0x00080000 ) )
		return -EIO;

	if (wait_for_eoc (pdev, module_to_offset(module)))
		return -EIO;

	*value = INPDW (module_address + APCI3200_REG_CHANNEL_VALUE);

	return 0;
}

int i_APCI3200_Read1AnalogInputChannel(struct pci_dev *pdev, struct Read1AnalogInputChannel_t *AnalogInputConfig)
{
	int ret;

	MARK();

	ret = i_APCI3200_SetConvertingTime(pdev, channel_to_module(AnalogInputConfig->b_Channel), AnalogInputConfig->dw_TimerReloadValue);
	if(ret)
		return ret;

	return 	i_APCI3200_Read1AnalogInputChannelFast (pdev, AnalogInputConfig->b_Channel, &(AnalogInputConfig->dw_ChannelValue));

}

int i_APCI3200_ReadCalibrationGainValueFast (struct pci_dev *pdev, unsigned char channel, unsigned long *value)
{
	int module = channel_to_module(channel);
	register unsigned long module_address = module_to_address(pdev,module);
	register uint32_t config = channel_to_index(channel);

	/* Test if diagnostic mode is to be used */
	if (i_APCI3200_GetDiagnosticMode (pdev, channel) == APCI3200_DIAGNOSTIC_OPEN_CONNECTION)
	{
		config = (config | 0x00000800);
	}

	/* Write the channel to configure */
	if( outl_module(module_address, APCI3200_REG_ACQUISITION_CONFIG, config ) )
		return -EIO;

	/* start Gain auto-calibration */
	if( outl_module(module_address, APCI3200_REG_AUTOCALIBRATION, 0x00040000) )
		return -EIO;

	/* Start the acquisition of calibration gain value */
	if( outl_module(module_address, APCI3200_REG_ACQUISITION_CONTROL, 0x00080000) )
		return -EIO;

	if (wait_for_eoc (pdev, module_to_offset(module)) )
		return -EIO;

	/* Stop the gain auto-calibration */
	if( outl_module(module_address, APCI3200_REG_AUTOCALIBRATION, 0x00000000) )
		return -EIO;

	*value = INPDW (module_address + APCI3200_REG_CHANNEL_VALUE);

	return 0;
}

int i_APCI3200_ReadCalibrationGainValue(struct pci_dev *pdev, struct Read1AnalogInputChannel_t *AnalogInputConfig)
{
	int ret;

	ret = i_APCI3200_SetConvertingTime(pdev, channel_to_module(AnalogInputConfig->b_Channel), AnalogInputConfig->dw_TimerReloadValue);
	if(ret)
		return ret;

	return i_APCI3200_ReadCalibrationGainValueFast (pdev, AnalogInputConfig->b_Channel, &(AnalogInputConfig->dw_ChannelValue));
}

int i_APCI3200_ReadCalibrationOffsetValueFast (struct pci_dev *pdev, unsigned char channel, unsigned long *value)
{
	int module = channel_to_module(channel);
	register unsigned long module_address = module_to_address(pdev,module);
	register uint32_t config = channel_to_index(channel);

	/* Test if diagnostic mode is to be used */
	if (i_APCI3200_GetDiagnosticMode (pdev, channel) == APCI3200_DIAGNOSTIC_OPEN_CONNECTION)
	{
		config = (config | 0x00000800);
	}

	/* Write the channel to configure */
	if( outl_module(module_address, APCI3200_REG_ACQUISITION_CONFIG, config ) )
		return -EIO;

	/* Start the offset auto-calibration */
	{
		unsigned long dw_Value = INPDW(module_address + APCI3200_REG_AUTOCALIBRATION);
		if( outl_module(module_address, APCI3200_REG_AUTOCALIBRATION, (dw_Value | 0x00020000) ) )
			return -EIO;
	}

	/* Start the acquisition of calibration offset value */
	if( outl_module(module_address, APCI3200_REG_ACQUISITION_CONTROL, 0x00080000) )
		return -EIO;

	/* Wait the end of the conversion (analog to digital) */
	if (wait_for_eoc (pdev, module_to_offset(module)))
		return -EIO;

	/* Stop the offset auto-calibration */
	if( outl_module(module_address, APCI3200_REG_AUTOCALIBRATION, 0x00000000) )
		return -EIO;

	/* Read the digital value of the calibration Offset */
	*value = INPDW (module_address + APCI3200_REG_CHANNEL_VALUE);

	return 0;
}

int i_APCI3200_ReadCalibrationOffsetValue(struct pci_dev *pdev, struct Read1AnalogInputChannel_t *AnalogInputConfig)
{
	int ret;

	ret = i_APCI3200_SetConvertingTime(pdev, channel_to_module(AnalogInputConfig->b_Channel), AnalogInputConfig->dw_TimerReloadValue);
	if(ret)
		return ret;

	return i_APCI3200_ReadCalibrationOffsetValueFast (pdev, AnalogInputConfig->b_Channel, &(AnalogInputConfig->dw_ChannelValue) );
}


/** Read the reference voltage for a given channel
*
* @param [in,out]	pdev	Pointer on the device structure.
* @param [in]		Channel	0 to 15 when unipolar, 0 to 7 when bipolar
* @param [out]		ReferenceVoltage 2500 mV or 1250 mV
* @param [out]		Unit always APCI3200_MILLIVOLT
*
* @retval 0		No error.
* @retval -EINVAL	Incorrect parameter
* @retval -1		Timeout.
*/

int i_APCI3200_ReadReferenceVoltage(struct pci_dev *pdev, uint8_t Channel, unsigned int* ReferenceVoltage, unsigned int* Unit)
{
	if( check_channel(Channel) )
		return -EINVAL;

	(*ReferenceVoltage) = apci3200_get_reference_voltage(pdev, Channel);
	(*Unit) = APCI3200_MILLIVOLT;
	return 0;
}

int i_APCI3200_SetConvertingTime(struct pci_dev *pdev, uint8_t module, unsigned long dw_TimerReloadValue)
{
	register unsigned long module_address = module_to_address(pdev,module);

	/* Set the convert timing unit */
	if( outl_module(module_address, APCI3200_REG_CONVERSION_BASE, APCI3200_UNIT_HZ) )
		return -EIO;

	/* Set the convert timing */
	if( outl_module(module_address, APCI3200_REG_CONVERSION_VALUE, dw_TimerReloadValue ) )
		return -EIO;
	return 0;
}
