#ifndef ADC_C
#define ADC_C

//----------------------------------
// Includes and forward declarations
//----------------------------------
#include <cyg/io/adc.h>
#include <cyg/io/devtab.h>
#include <cyg/io/io.h>

//--------------------
// Function prototypes
//--------------------
static Cyg_ErrNo adc_read(cyg_io_handle_t a_handle, 
                          void * a_buf, 
			  cyg_uint32 * a_len);
static Cyg_ErrNo adc_set_config(cyg_io_handle_t a_handle, 
                                cyg_uint32 a_key, 
				const void * a_buf, 
				cyg_uint32 * a_len);

//-------------------------------------------
// Register the device driver with the kernel
//-------------------------------------------
DEVIO_TABLE(adc_devio,
            0,
            adc_read,
	    0,
            0,
            adc_set_config);

//-----------------------
// The callback functions
//-----------------------
ADC_CALLBACKS(adc_callbacks);

//------------------------
// Funcion implementations
//------------------------
// adc_read
static Cyg_ErrNo adc_read(cyg_io_handle_t a_handle, void * a_buf, cyg_uint32 * a_len)
{
  cyg_devtab_entry_t * t = (cyg_devtab_entry_t *)a_handle;
  adc_channel * chan = (adc_channel *)t->priv;
  adc_funs * funs = chan->m_funs;

  double result;

  if(*a_len != sizeof(double))
    return -EINVAL;

  result = funs->adc_read(chan);   // between -1.0 and 1.0
  *(double *)a_buf = result * chan->m_scaling + chan->m_offset;

  return ENOERR;
}

// adc_set_config
static Cyg_ErrNo adc_set_config(cyg_io_handle_t a_handle, cyg_uint32 a_key, const void * a_buf, cyg_uint32 * a_len)
{
  Cyg_ErrNo result = ENOERR;
  cyg_devtab_entry_t * t = (cyg_devtab_entry_t *)a_handle;
  adc_channel * chan = (adc_channel *)t->priv;

  switch(a_key)
  {
    case ADC_IO_SET_CONFIG_10V_FULL_SCALE:
      {
        chan->m_span = 5;
        chan->m_scaling = chan->m_span * chan->m_scaling_usr * chan->m_scaling_cal; 
        chan->m_offset  = chan->m_scaling_usr * (chan->m_scaling_cal * chan->m_offset_cal + chan->m_offset_usr);
      }
      break;

    case ADC_IO_SET_CONFIG_20V_FULL_SCALE:
      {
        chan->m_span = 10;
        chan->m_scaling = chan->m_span * chan->m_scaling_usr * chan->m_scaling_cal; 
        chan->m_offset  = chan->m_scaling_usr * (chan->m_scaling_cal * chan->m_offset_cal + chan->m_offset_usr);
      }
      break;

    case ADC_IO_SET_CONFIG_SCALING:
      {
        if(*a_len != sizeof(double))
          return -EINVAL;

        chan->m_scaling_usr = *(double *)a_buf;
        chan->m_scaling = chan->m_span * chan->m_scaling_usr * chan->m_scaling_cal; 
        chan->m_offset  = chan->m_scaling_usr * (chan->m_scaling_cal * chan->m_offset_cal + chan->m_offset_usr);
      }
      break;

    case ADC_IO_SET_CONFIG_OFFSET:
      {
        if(*a_len != sizeof(double))
          return -EINVAL;

        chan->m_offset_usr = *(double *)a_buf;
        chan->m_scaling = chan->m_span * chan->m_scaling_usr * chan->m_scaling_cal; 
        chan->m_offset  = chan->m_scaling_usr * (chan->m_scaling_cal * chan->m_offset_cal + chan->m_offset_usr);
      }
      break;

    case ADC_IO_SET_CONFIG_SCALING_CAL:
      {
        if(*a_len != sizeof(double))
          return -EINVAL;

        chan->m_scaling_cal = *(double *)a_buf;
        chan->m_scaling = chan->m_span * chan->m_scaling_usr * chan->m_scaling_cal; 
        chan->m_offset  = chan->m_scaling_usr * (chan->m_scaling_cal * chan->m_offset_cal + chan->m_offset_usr);
      }
      break;

    case ADC_IO_SET_CONFIG_OFFSET_CAL:
      {
        if(*a_len != sizeof(double))
          return -EINVAL;

        chan->m_offset_cal = *(double *)a_buf;
        chan->m_scaling = chan->m_span * chan->m_scaling_usr * chan->m_scaling_cal; 
        chan->m_offset  = chan->m_scaling_usr * (chan->m_scaling_cal * chan->m_offset_cal + chan->m_offset_usr);
      }
      break;

    default:
      result = -EINVAL;
      break;
  }

  return result;
}

#endif // ADC_C
