/******************************************************************************
    uPD7002 Analogue to Digital Converter

    MESS Driver By:

	Gordon Jefferyes
	mess_bbc@gjeffery.dircon.co.uk

******************************************************************************/

#include "driver.h"
#include "upd7002.h"


typedef struct _uPD7002_t uPD7002_t;
struct _uPD7002_t
{
	/* Pointer to our interface */
	const uPD7002_interface *intf;

	/* Status Register
		D0 and D1 define the currently selected input channel
		D2 flag output
		D3 0 = 8 bit mode   1 = 12 bit mode
		D4 2nd MSB of conversion
		D5     MSB of conversion
		D6 0 = busy, 1 = not busy    (~busy)
		D7 0 = conversion completed, 1 = conversion not completed  (~EOC)
	*/
	int status;

	/* High data byte
		This byte contains the 8 most significant bits of the analogue to digital conversion. */
	int data1;

	/* Low data byte
		In 12 bit mode: Bits 7 to 4 define the four low order bits of the conversion.
		In  8 bit mode. All bits 7 to 4 are inaccurate.
		Bits 3 to 0 are always set to low. */
	int data0;


	/* temporary store of the next A to D conversion */
	int digitalvalue;

	/* this counter is used to check a full end of conversion has been reached
	if the uPD7002 is half way through one conversion and a new conversion is requested
	the counter at the end of the first conversion will not match and not be processed
	only then at the end of the second conversion will the conversion complete function run */
	int conversion_counter;
};


/*****************************************************************************
 Implementation
*****************************************************************************/

INLINE uPD7002_t *get_safe_token(const device_config *device)
{
	assert(device != NULL);
	assert(device->token != NULL);

	return (uPD7002_t *)device->token;
}

READ8_DEVICE_HANDLER ( uPD7002_EOC_r )
{
	uPD7002_t *uPD7002 = get_safe_token(device);
	return (uPD7002->status>>7)&0x01;
}


static TIMER_CALLBACK(uPD7002_conversioncomplete)
{
	const device_config *device = ptr;
	uPD7002_t *uPD7002 = get_safe_token(device);

	int counter_value = param;
	if (counter_value==uPD7002->conversion_counter)
	{
		// this really always does a 12 bit conversion
		uPD7002->data1 = uPD7002->digitalvalue>>8;
		uPD7002->data0 = uPD7002->digitalvalue&0xf0;

		// set the status register with top 2 MSB, not busy and conversion complete
		uPD7002->status = (uPD7002->status & 0x0f)|((uPD7002->data1 & 0xc0)>>2)|0x40;

		// call the EOC function with EOC from status
		// uPD7002_EOC_r(0) this has just been set to 0
		if (uPD7002->intf->EOC_func) (uPD7002->intf->EOC_func)(device,0);
		uPD7002->conversion_counter=0;
	}
}


READ8_DEVICE_HANDLER ( uPD7002_r )
{
	uPD7002_t *uPD7002 = get_safe_token(device);

	switch(offset&0x03)
	{
		case 0:
			return uPD7002->status;

		case 1:
			return uPD7002->data1;

		case 2: case 3:
			return uPD7002->data0;
	}
	return 0;
}



WRITE8_DEVICE_HANDLER ( uPD7002_w )
{
	uPD7002_t *uPD7002 = get_safe_token(device);
	/* logerror("write to uPD7002 $%02X = $%02X\n",offset,data); */

	switch(offset&0x03)
	{
		case 0:
		/*
		Data Latch/AD start
			D0 and D1 together define which one of the four input channels is selected
			D2 flag input, normally set to 0????
			D3 defines whether an 8 (0) or 12 (1) bit resolution conversion should occur
			D4 to D7 not used.

			an 8  bit conversion typically takes 4ms
			an 12 bit conversion typically takes 10ms

			writing to this register will initiate a conversion.
		*/

		/* set D6=0 busy ,D7=1 conversion not complete */
		uPD7002->status=(data & 0x0f) | 0x80;

		// call the EOC function with EOC from status
		// uPD7002_EOC_r(0) this has just been set to 1
		if (uPD7002->intf->EOC_func) uPD7002->intf->EOC_func(device, 1);

		/* the uPD7002 works by sampling the analogue value at the start of the conversion
		   so it is read hear and stored until the end of the A to D conversion */

		// this function should return a 16 bit value.
		uPD7002->digitalvalue = uPD7002->intf->get_analogue_func(device, uPD7002->status & 0x03);

		uPD7002->conversion_counter++;

		// call a timer to start the conversion
		if (uPD7002->status & 0x08)
		{
			// 12 bit conversion takes 10ms
			timer_set(device->machine, ATTOTIME_IN_MSEC(10), (void *)device, uPD7002->conversion_counter, uPD7002_conversioncomplete);
		} else {
			// 8 bit conversion takes 4ms
			timer_set(device->machine, ATTOTIME_IN_MSEC(4),  (void *)device, uPD7002->conversion_counter, uPD7002_conversioncomplete);
		}
		break;

		case 1: case 2:
		/* Nothing */
		break;

		case 3:
		/* Test Mode: Used for inspecting the device, The data input-output terminals assume an input
			  state and are connected to the A/D counter. Therefore, the A/D conversion data
			  read out after this is meaningless.
		*/
		break;
	}
}

/* Device Interface */

static DEVICE_START( uPD7002 )
{
	uPD7002_t *uPD7002 = get_safe_token(device);
	// validate arguments

	assert(device != NULL);
	assert(device->tag != NULL);
	assert(device->static_config != NULL);

	uPD7002->intf = device->static_config;
	uPD7002->status = 0;
	uPD7002->data1 = 0;
	uPD7002->data0 = 0;
	uPD7002->digitalvalue = 0;
	uPD7002->conversion_counter = 0;

	// register for state saving
	state_save_register_item(device->machine, "uPD7002", device->tag, 0, uPD7002->status);
	state_save_register_item(device->machine, "uPD7002", device->tag, 0, uPD7002->data1);
	state_save_register_item(device->machine, "uPD7002", device->tag, 0, uPD7002->data0);
	state_save_register_item(device->machine, "uPD7002", device->tag, 0, uPD7002->digitalvalue);
	state_save_register_item(device->machine, "uPD7002", device->tag, 0, uPD7002->conversion_counter);
}

static DEVICE_RESET( uPD7002 )
{
	uPD7002_t *uPD7002 = get_safe_token(device);
	uPD7002->status = 0;
	uPD7002->data1 = 0;
	uPD7002->data0 = 0;
	uPD7002->digitalvalue = 0;
	uPD7002->conversion_counter = 0;
}

static DEVICE_SET_INFO( uPD7002 )
{
	switch (state)
	{
		/* no parameters to set */
	}
}

DEVICE_GET_INFO( uPD7002 )
{
	switch (state)
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_INT_TOKEN_BYTES:					info->i = sizeof(uPD7002_t);					break;
		case DEVINFO_INT_INLINE_CONFIG_BYTES:			info->i = 0;								break;
		case DEVINFO_INT_CLASS:							info->i = DEVICE_CLASS_PERIPHERAL;			break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_SET_INFO:						info->set_info = DEVICE_SET_INFO_NAME(uPD7002); break;
		case DEVINFO_FCT_START:							info->start = DEVICE_START_NAME(uPD7002);		break;
		case DEVINFO_FCT_STOP:							/* Nothing */								break;
		case DEVINFO_FCT_RESET:							info->reset = DEVICE_RESET_NAME(uPD7002);		break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:							strcpy(info->s, "uPD7002");						break;
		case DEVINFO_STR_FAMILY:						strcpy(info->s, "uPD7002");						break;
		case DEVINFO_STR_VERSION:						strcpy(info->s, "1.0");							break;
		case DEVINFO_STR_SOURCE_FILE:					strcpy(info->s, __FILE__);							break;
		case DEVINFO_STR_CREDITS:						strcpy(info->s, "Copyright MESS Team");			break;
	}
}
