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

    National Semiconductor ADC0831 / ADC0832 / ADC0834 / ADC0838

    8-Bit serial I/O A/D Converters with Muliplexer Options

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

#include "emu.h"
#include "adc083x.h"

#define VERBOSE_LEVEL ( 0 )

INLINE void ATTR_PRINTF( 3, 4 ) verboselog( int n_level, running_machine &machine, const char *s_fmt, ... )
{
	if( VERBOSE_LEVEL >= n_level )
	{
		va_list v;
		char buf[ 32768 ];
		va_start( v, s_fmt );
		vsprintf( buf, s_fmt, v );
		va_end( v );
		logerror( "%s: %s", machine.describe_context( ), buf );
	}
}

/***************************************************************************
    PARAMETERS
***************************************************************************/

enum
{
	STATE_IDLE,
	STATE_WAIT_FOR_START,
	STATE_SHIFT_MUX,
	STATE_MUX_SETTLE,
	STATE_OUTPUT_MSB_FIRST,
	STATE_WAIT_FOR_SE,
	STATE_OUTPUT_LSB_FIRST,
	STATE_FINISHED
};

/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

const device_type ADC0831 = &device_creator<adc0831_device>;
const device_type ADC0832 = &device_creator<adc0832_device>;
const device_type ADC0834 = &device_creator<adc0834_device>;
const device_type ADC0838 = &device_creator<adc0838_device>;

adc083x_device::adc083x_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source)
	: device_t(mconfig, type, name, tag, owner, clock, shortname, source),
	m_cs(0),
	m_clk(0),
	m_di(0),
	m_se(0),
	m_do(1),
	m_sgl(0),
	m_odd(0),
	m_sel1(0),
	m_sel0(0),
	m_state(STATE_IDLE),
	m_bit(0),
	m_output(0)
{
}

adc0831_device::adc0831_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: adc083x_device(mconfig, ADC0831, "ADC0831", tag, owner, clock, "adc0831", __FILE__)
{
	m_mux_bits = 0;
}

adc0832_device::adc0832_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: adc083x_device(mconfig, ADC0832, "ADC0832", tag, owner, clock, "adc0832", __FILE__)
{
	m_mux_bits = 2;
}

adc0834_device::adc0834_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: adc083x_device(mconfig, ADC0834, "ADC0834", tag, owner, clock, "adc0834", __FILE__)
{
	m_mux_bits = 3;
}

adc0838_device::adc0838_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: adc083x_device(mconfig, ADC0838, "ADC0838", tag, owner, clock, "adc0838", __FILE__)
{
	m_mux_bits = 4;
}

/*-------------------------------------------------
    adc083x_device::device_start
-------------------------------------------------*/

void adc083x_device::device_start()
{
	clear_sars();

	/* resolve callbacks */
	m_input_callback.bind_relative_to(*owner());

	/* register for state saving */
	save_item( NAME(m_cs) );
	save_item( NAME(m_clk) );
	save_item( NAME(m_di) );
	save_item( NAME(m_se) );
	save_item( NAME(m_sars) );
	save_item( NAME(m_do) );
	save_item( NAME(m_sgl) );
	save_item( NAME(m_odd) );
	save_item( NAME(m_sel1) );
	save_item( NAME(m_sel0) );
	save_item( NAME(m_state) );
	save_item( NAME(m_bit) );
	save_item( NAME(m_output) );
	save_item( NAME(m_mux_bits) );
}

/*-------------------------------------------------
    adc083x_device::clear_sars
-------------------------------------------------*/

void adc083x_device::clear_sars()
{
	if( type() == ADC0834 || type() == ADC0838 )
	{
		m_sars = 1;
	}
	else
	{
		m_sars = 0;
	}
}

/*-------------------------------------------------
    adc083x_device::cs_write
-------------------------------------------------*/

WRITE_LINE_MEMBER( adc083x_device::cs_write )
{
	if( m_cs != state )
	{
		verboselog( 2, machine(), "adc083x_cs_write( %s, %d )\n", tag(), state );
	}

	if( m_cs == 0 && state != 0 )
	{
		m_state = STATE_IDLE;
		clear_sars();
		m_do = 1;
	}

	if( m_cs != 0 && state == 0 )
	{
		if( type() == ADC0831 )
		{
			m_state = STATE_MUX_SETTLE;
		}
		else
		{
			m_state = STATE_WAIT_FOR_START;
		}

		clear_sars();
		m_do = 1;
	}

	m_cs = state;
}

/*-------------------------------------------------
    adc083x_device::conversion
-------------------------------------------------*/

UINT8 adc083x_device::conversion()
{
	int result;
	int positive_channel = ADC083X_AGND;
	int negative_channel = ADC083X_AGND;
	double positive = 0;
	double negative = 0;
	double gnd = m_input_callback(ADC083X_AGND);
	double vref = m_input_callback(ADC083X_VREF);

	if( type() == ADC0831 )
	{
		positive_channel = ADC083X_CH0;
		negative_channel = ADC083X_CH1;
	}
	else if( type() == ADC0832 )
	{
		positive_channel = ADC083X_CH0 + m_odd;
		if( m_sgl == 0 )
		{
			negative_channel = positive_channel ^ 1;
		}
		else
		{
			negative_channel = ADC083X_AGND;
		}
	}
	else if( type() == ADC0834 )
	{
		positive_channel = ADC083X_CH0 + m_odd + ( m_sel1 * 2 );
		if( m_sgl == 0 )
		{
			negative_channel = positive_channel ^ 1;
		}
		else
		{
			negative_channel = ADC083X_AGND;
		}
	}
	else if( type() == ADC0838 )
	{
		positive_channel = ADC083X_CH0 + m_odd + ( m_sel0 * 2 ) + ( m_sel1 * 4 );
		if( m_sgl == 0 )
		{
			negative_channel = positive_channel ^ 1;
		}
		else
		{
			negative_channel = ADC083X_COM;
		}
	}

	if( positive_channel != ADC083X_AGND )
	{
		positive = m_input_callback(positive_channel) - gnd;
	}

	if( negative_channel != ADC083X_AGND )
	{
		negative = m_input_callback(negative_channel) - gnd;
	}

	result = (int) ( ( ( positive - negative ) * 255 ) / vref );
	if( result < 0 )
	{
		result = 0;
	}
	else if( result > 255 )
	{
		result = 255;
	}

	return result;
}

/*-------------------------------------------------
    adc083x_device::clk_write
-------------------------------------------------*/

WRITE_LINE_MEMBER( adc083x_device::clk_write )
{
	if( m_clk != state )
	{
		verboselog( 2, machine(), "adc083x_clk_write( %s, %d )\n", tag(), state );
	}

	if( m_cs == 0 )
	{
		if( m_clk == 0 && state != 0 )
		{
			switch( m_state )
			{
			case STATE_WAIT_FOR_START:
				if( m_di != 0 )
				{
					verboselog( 1, machine(), "adc083x %s got start bit\n", tag() );
					m_state = STATE_SHIFT_MUX;
					m_sars = 0;
					m_sgl = 0;
					m_odd = 0;
					m_sel1 = 0;
					m_sel0 = 0;
					m_bit = 0;
				}
				else
				{
					verboselog( 1, machine(), "adc083x %s not start bit\n", tag() );
				}
				break;

			case STATE_SHIFT_MUX:
				switch( m_bit )
				{
				case 0:
					if( m_di != 0 )
					{
						m_sgl = 1;
					}
					verboselog( 1, machine(), "adc083x %s sgl <- %d\n", tag(), m_sgl );
					break;

				case 1:
					if( m_di != 0 )
					{
						m_odd = 1;
					}
					verboselog( 1, machine(), "adc083x %s odd <- %d\n", tag(), m_odd );
					break;

				case 2:
					if( m_di != 0 )
					{
						m_sel1 = 1;
					}
					verboselog( 1, machine(), "adc083x %s sel1 <- %d\n", tag(), m_sel1 );
					break;

				case 3:
					if( m_di != 0 )
					{
						m_sel0 = 1;
					}
					verboselog( 1, machine(), "adc083x %s sel0 <- %d\n", tag(), m_sel0 );
					break;
				}

				m_bit++;
				if( m_bit == m_mux_bits )
				{
					m_state = STATE_MUX_SETTLE;
				}

				break;

			case STATE_WAIT_FOR_SE:
				m_sars = 0;
				if( type() == ADC0838 && m_se != 0 )
				{
					verboselog( 1, machine(), "adc083x %s not se\n", tag() );
				}
				else
				{
					verboselog( 1, machine(), "adc083x %s got se\n", tag() );
					m_state = STATE_OUTPUT_LSB_FIRST;
					m_bit = 1;
				}
				break;
			}
		}

		if( m_clk != 0 && state == 0 )
		{
			switch( m_state )
			{
			case STATE_MUX_SETTLE:
				verboselog( 1, machine(), "adc083x %s mux settle\n", tag() );
				m_output = conversion();
				m_state = STATE_OUTPUT_MSB_FIRST;
				m_bit = 7;
				clear_sars();
				m_do = 0;
				break;

			case STATE_OUTPUT_MSB_FIRST:
				m_do = ( m_output >> m_bit ) & 1;
				verboselog( 1, machine(), "adc083x %s msb %d -> %d\n", tag(), m_bit, m_do );

				m_bit--;
				if( m_bit < 0 )
				{
					if( type() == ADC0831 )
					{
						m_state = STATE_FINISHED;
					}
					else
					{
						m_state = STATE_WAIT_FOR_SE;
					}
				}
				break;

			case STATE_OUTPUT_LSB_FIRST:
				m_do = ( m_output >> m_bit ) & 1;
				verboselog( 1, machine(), "adc083x %s lsb %d -> %d\n", tag(), m_bit, m_do );

				m_bit++;
				if( m_bit == 8 )
				{
					m_state = STATE_FINISHED;
				}
				break;

			case STATE_FINISHED:
				m_state = STATE_IDLE;
				m_do = 0;
				break;
			}
		}
	}

	m_clk = state;
}

/*-------------------------------------------------
    adc083x_device::di_write
-------------------------------------------------*/

WRITE_LINE_MEMBER( adc083x_device::di_write )
{
	if( m_di != state )
	{
		verboselog( 2, machine(), "adc083x_di_write( %s, %d )\n", tag(), state );
	}

	m_di = state;
}

/*-------------------------------------------------
    adc083x_device::se_write
-------------------------------------------------*/

WRITE_LINE_MEMBER( adc083x_device::se_write )
{
	if( m_se != state )
	{
		verboselog( 2, machine(), "adc083x_se_write( %s, %d )\n", tag(), state );
	}

	m_se = state;
}

/*-------------------------------------------------
    adc083x_device::sars_read
-------------------------------------------------*/

READ_LINE_MEMBER( adc083x_device::sars_read )
{
	verboselog( 1, machine(), "adc083x_sars_read( %s ) %d\n", tag(), m_sars );
	return m_sars;
}

/*-------------------------------------------------
    adc083x_device::do_read
-------------------------------------------------*/

READ_LINE_MEMBER( adc083x_device::do_read )
{
	verboselog( 1, machine(), "adc083x_do_read( %s ) %d\n", tag(), m_do );
	return m_do;
}
