#include <avr/io.h>

#include "avradc.h"
#include "avrtime.h"

enum adc_cmd { ADC_NOP, ADC_STOP, ADC_START };
enum { ADC_OFF, ADC_ON, ADC_PWAIT, ADC_WAITCONV, ADC_POFF };
static uint8_t adc_st;
static uint32_t adc_time;
static enum adc_cmd cmd;
static uint8_t adcsra;
static uint32_t to1;

void adc_timeinit(void) {
  //const uint32_t fmin =  50000;
  const uint32_t fmax = 200000;

  uint8_t ps = 2; // smallest prescaler value
  uint32_t ftest = (time_sysosc >> time_syspsc)/2;

  while(ps < 7 && ftest > fmax) {
    ps++;
    ftest >>= 1;
  }

  adcsra = ps;
  to1 = time_us2clk(100);
}

static void powerup(void) {
  PRR &= ~_BV(PRADC);
  ADCSRA |= _BV(ADEN) | adcsra; // other bits set with adc_set()
  ADCSRB = 0; // free running mode if ADATE in ADCSRA is set
  DIDR0 = 0x3f; // adc0 .. 5: no digital input
  adc_time = time_current;
}

int8_t adc_run(uint16_t *adc) {
  int8_t ix = 0;

  switch (adc_st) {
  case ADC_OFF:
    switch (cmd) {
    case ADC_START:
      powerup();
      adc_st = ADC_PWAIT;
      break;
    default:
      break;
    }
    break;

  case ADC_PWAIT:
    switch (cmd) {
    case ADC_STOP:
      adc_st = ADC_POFF;
      break;
    default:
      if ( time_current - adc_time > to1) {
	ADCSRA |= _BV(ADSC);
	adc_st = ADC_WAITCONV;
      }
      break;
    }
    break;

  case ADC_ON:
    switch (cmd) {
    case ADC_START:
      ADCSRA |= _BV(ADSC);
      adc_st = ADC_WAITCONV;
      break;
    case ADC_STOP:
      adc_st = ADC_POFF;
      break;
    default:
      break;
    }
    break;

  case ADC_WAITCONV: 
    switch (cmd) {
    case ADC_STOP:
      adc_st = ADC_POFF;
      break;
    default:
      if ((ADCSRA & _BV(ADSC)) == 0) {
	ix = 1;
	*adc = ADC;
	adc_st = ADC_ON;
      }
      break;
    }
    break;

  case ADC_POFF:
    switch (cmd) {
    case ADC_START:
      powerup();
      adc_st = ADC_PWAIT;
    default:
      PRR |= _BV(PRADC);
      ADCSRA = adcsra;
      adc_st = ADC_OFF;
    }
    break;

  }

  cmd = 0;
  return ix;
}

void adc_start(uint8_t admux) {
  ADMUX = admux;
  cmd = ADC_START;
}

void adc_stop(void) {
  cmd = ADC_STOP;
}
