/* Name: main.c
 * Project: SI570 USB controller
 * Author: Tom Baier DG8SAQ
 * based on ObDev's AVR USB driver by Christian Starkjohann
 * Creation Date: 2006-04-23
 * Tabsize: 4
 * Copyright: (c) 2006 by OBJECTIVE DEVELOPMENT Software GmbH
 * License: Proprietary, free under certain conditions. See Documentation.
 * This Revision: V1.4/July 30th, 2008
 */

/*
 * Fuse bit information:
 * Fuse high byte:
 * 0xdd = 0 1 0 1   1 1 0 1
 *        ^ ^ ^ ^   ^ \-+-/ 
 *        | | | |   |   +------ BODLEVEL 2..0 (brownout trigger level -> 2.7V)
 *        | | | |   +---------- EESAVE (preserve EEPROM on Chip Erase -> not preserved)
 *        | | | +-------------- WDTON (watchdog timer always on -> disable)
 *        | | +---------------- SPIEN (enable serial programming -> enabled)
 *        | +------------------ DWEN (debug wire enable)
 *        +-------------------- RSTDISBL (disable external reset -> disabled)
 *
 * Fuse low byte:
 * 0xe1 = 1 1 1 0   0 0 0 1
 *        ^ ^ \+/   \--+--/
 *        | |  |       +------- CKSEL 3..0 (clock selection -> HF PLL)
 *        | |  +--------------- SUT 1..0 (BOD enabled, fast rising power)
 *        | +------------------ CKOUT (clock output on CKOUT pin -> disabled)
 *        +-------------------- CKDIV8 (divide clock by 8 -> don't divide) 
 */

#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <stdint.h>
#include "usbdrv.h"

/*
Pin assignment:
PB0, PB2 = USB data lines
PB1 = SDA
PB3 = SCL
PB4 = user defined
PB5 = user defined
*/

#define BIT_SDA 1
#define BIT_SCL 3
#define SDA (1 << BIT_SDA)
#define SCL (1 << BIT_SCL)
#define USERP4 (1 << 4)
#define USERP5 (1 << 5)
#define RXTX USERP4
#define CWKEY (USERP5 | SDA)


//EEPROM contents
#define RC_CAL_VALUE 0	//1 byte
#define F_CAL_STATUS 1  //1 byte
#define F_CRYST 2		//4 bytes
#define INIT_SI570 6	//1 byte
#define INIT_FREQ 7		//6 byte

#define OPEN_COLLECTOR	//comment out if i2c lines should not be open collectors 

#ifndef OPEN_COLLECTOR
#include "I2C.h"
#else 
#include "I2Copencollector.h"
#endif 
 
#include "SI570.h"

extern uchar usbDeviceAddr;
char command;

uint8_t mcusr_mirror __attribute__((section (".noinit")));
void get_mcusr(void) \
      __attribute__((naked)) \
      __attribute__((section(".init3")));
    void get_mcusr(void)
    {
      mcusr_mirror = MCUSR;
      MCUSR = 0;
      wdt_disable();
    }

/* ------------------------------------------------------------------------- */
/* ------------------------ interface to USB driver ------------------------ */
/* ------------------------------------------------------------------------- */

void safefreq(void)
{
uchar i;
if (eeprom_read_byte((uint8_t *)INIT_SI570)!=0xff) 	//  read init status for SI570
	{
	for (i=0;i<6;i++) eeprom_write_byte((uint8_t *)(i+INIT_FREQ),SI570_data[i]);
	}
}


USB_PUBLIC uchar usbFunctionWrite(uchar *data, uchar len) //sends len bytes to SI570
{
uchar i;
    if (command==0x30)					// get register contents and load to SI570
		{
		if (len<6) return 1;
		for (i=0;i<6;i++) SI570_data[i]=data[i];
		safefreq();		   
		SI570_Load();
		return 1;
		}
	if (len<4) return 1;
	if (command==0x32)					// get frequency and load to SI570
		{	
		for (i=0;i<4;i++) freq.bytes[i]=data[i];
		if (SI570CalcRegs()>0) 
		    {
		    safefreq();	
		    SI570_Load();
		    }	
		return 1;
		}
	if (command==0x33)					// write new crystal frequency to EEPROM
		{	
		for (i=0;i<4;i++) eeprom_write_byte((uint8_t *)(F_CRYST+i), data[i]);
		eeprom_write_byte((uint8_t *)(F_CAL_STATUS), 0);
		for (i=0;i<4;i++) fcr.bytes[i]=data[i];
		return 1;
		}
	return 1;
}

USB_PUBLIC uchar usbFunctionSetup(uchar data[8])
{
usbRequest_t *rq = (void *)data;
static uchar    replyBuf[6];
uchar i;
    usbMsgPtr = replyBuf;
	if(rq->bRequest == 0){       		// ECHO value
        replyBuf[0] = data[2];			// rq->bRequest identical data[1]!
        replyBuf[1] = data[3];
        return 2;
    }
	if(rq->bRequest == 1){       		// set port directions
        DDRB = data[2] & 
		 ~((1 << USB_CFG_DMINUS_BIT) 
		 | (1 << USB_CFG_DPLUS_BIT));   // protect USB interface
        return 0;
    }
	if(rq->bRequest == 2){       		// read ports 
        replyBuf[0] = PINB;
        return 1;
	}
	if(rq->bRequest == 3){       		// read port states 
        replyBuf[0] = PORTB;
        return 1;
    }
	if(rq->bRequest == 4){       		// set ports 
        PORTB = data[2] & 
		 ~((1 << USB_CFG_DMINUS_BIT) 
		 | (1 << USB_CFG_DPLUS_BIT));   // protect USB interface
        return 0;
    }
	if(rq->bRequest == 5){       		// send I2C start sequence 
        I2CSendStart();
        return 0;
    }	
	if(rq->bRequest == 6){       		// send I2C stop sequence
        I2CSendStop();
        return 0;
    }	
	if(rq->bRequest == 7){       		// send byte to I2C 
		I2CErrors = 0;					// reset error counter
        I2CSendByte(data[2]);
		replyBuf[0] = I2CErrors;		// return number of I2C transmission errors	
        return 1;
    }	
	if(rq->bRequest == 8){       		// send word to I2C 
		I2CErrors = 0;					// reset error counter
        I2CSendByte(data[2]);
        I2CSendByte(data[3]);
		replyBuf[0] = I2CErrors;		// return number of I2C transmission errors	
        return 1;
    }	
	if(rq->bRequest == 9){       		// send dword to I2C 
		I2CErrors = 0;					// reset error counter
        I2CSendByte(data[2]);
        I2CSendByte(data[3]);
        I2CSendByte(data[4]);
        I2CSendByte(data[5]);
		replyBuf[0] = I2CErrors;		// return number of I2C transmission errors	
        return 1;
    }
	if(rq->bRequest == 0xa){       		// send word to I2C with start and stop sequence
        I2CSendStart();					// send start sequence
        I2CSendByte(data[2] & 0xFE);  	// send device address from "value"
        I2CSendByte(data[4]);			// send data from "index"
        I2CSendStop();					// send stop sequence
		replyBuf[0] = I2CErrors;		// return number of I2C transmission errors									
        return 1;
    }	
	if(rq->bRequest == 0xb){       		// receive word from I2C with start and stop sequence
        I2CSendStart();
        I2CSendByte(data[2] | 1);		// send device address from "value"
        replyBuf[1] = I2CReceiveLastByte();	// receive data and return
        I2CSendStop();
		replyBuf[0] = I2CErrors;		// return number of I2C transmission errors	
        return 2;
	}
	if(rq->bRequest == 0xc){       		// modify I2C clock
        I2C_MINTIME = data[2]+256*data[3];// set time constant from "value"
        return 0;
    }
	/*
	if(rq->bRequest == 0xd){       		// read OSCCAL to "value"
        replyBuf[0] = OSCCAL;
        return 1;
    }
	if(rq->bRequest == 0xe){       		// Write "value" to OSCCAL
        OSCCAL = data[2];
        return 0;
    }
	*/
	if(rq->bRequest == 0xf){       		// Reset by Watchdog
        for (;;){};
        return 0;
    }
	if(rq->bRequest == 0x10){ 			// EEPROM write byte value=address, index=data
	    eeprom_write_byte((uint8_t *)(data[2]+256*data[3]), data[4]);  
        return 0;
    }
	if(rq->bRequest == 0x11){       	// EEPROM read byte "value"=address
        replyBuf[0] = 
		   eeprom_read_byte((uint8_t *)(data[2]+256*data[3]));
        return 1;
    }
	/*
	if(rq->bRequest == 0x13){       	// return usb device address
        replyBuf[0] = usbDeviceAddr;
        return 1;
    }
	*/
/////SI570 specific code/////////////////////////////////////////////////////
	if(rq->bRequest == 0x20){       	// SI570: write byte from register index
        I2CSendStart();					
        I2CSendByte((data[2]<<1)&0xFE);	// send device address from "value" low
		I2CSendByte(data[3]);			// send Byte address from "value" high
		I2CSendByte(data[4]);	    	// send data from "index" low
        I2CSendStop();
		replyBuf[0] = I2CErrors;		// return number of I2C transmission errors	
        return 1;
	}
	if(rq->bRequest == 0x21){       	// SI570: read byte to register index
        I2CSendStart();					
        I2CSendByte((data[2]<<1)&0xFE); // send device address from "value" low
		I2CSendByte(data[3]);			// send Byte address from "value" high
		I2CSendStart();
		I2CSendByte((data[2]<<1)|1);	// send device address from "value" low, ready for read
        replyBuf[1] = I2CReceiveLastByte();	// receive data and return
        I2CSendStop();
		replyBuf[0] = I2CErrors;		// return number of I2C transmission errors	
        return 2;
	}
	if(rq->bRequest == 0x22){       	// SI570: freeze NCO 
		I2C_adr = data[2];
		SI570_freezeNCO();
		replyBuf[0] = I2CErrors;		// return number of I2C transmission errors	
        return 1;
	}
	if(rq->bRequest == 0x23){       	// SI570: unfreeze NCO
		I2C_adr = data[2];
		SI570_unfreezeNCO();
		replyBuf[0] = I2CErrors;		// return number of I2C transmission errors	
        return 1;
	}
	if((rq->bRequest >= 0x30)&
	  (rq->bRequest <  0x37)){       	// use usbFunctionWrite to transfer data
		I2C_adr = data[2];	
		byte_adr= data[3];
		command=rq->bRequest;			
        return 0xff;
	
/*	if(rq->bRequest == 0x3e){       	// debug: read out calculated frequency control registers
		for (i=0;i<6;i++){
			replyBuf[i] = SI570_data[i];// copy data bytes
			}
        return 6;}*/
	}
	if(rq->bRequest == 0x3f){       	// SI570: read out frequency control registers
        I2CSendStart();					// read all registers in one block
        I2CSendByte(data[2]<<1);	    // send device address from "value" lo
		I2CSendByte(7);			    	// send Byte address 7 of first register
		I2CSendStart();	
		I2CSendByte((data[2]<<1)|1);	// send device address from "value" lo for reading
		for (i=0;i<5;i++){
			replyBuf[i] = I2CReceiveByte();	// receive data byte
			}
		replyBuf[5] = I2CReceiveLastByte(); // receive without Acknowledge
        I2CSendStop(); 
	/*	for (i=0;i<6;i++){				// alternatively, read register after register
      	I2CSendStart();					
        I2CSendByte((data[2]<<1)&0xFE); // send device address from "value" low
		I2CSendByte(7+i);				// send Byte address from "value" high
		I2CSendStart();
		I2CSendByte((data[2]<<1)|1);	// send device address from "value" low, ready for read
        replyBuf[i] = I2CReceiveLastByte();	// receive data without Acknowledge and return
        I2CSendStop();
		}*/
        return 6;
	}
	if(rq->bRequest == 0x40){       	// return number of I2C transmission errors
		replyBuf[0] = I2CErrors;		
        return 1;
	}
  	if(rq->bRequest == 0x41){       	// set/reset init freq status
        eeprom_write_byte((uint8_t *)INIT_SI570,data[2]); //status: low byte of value = I2C address
        return 0;
    } 
	if(rq->bRequest == 0x50){       	// set RXTX and get cw-key status
	    if (data[2] == 0) PORTB = PORTB & ~RXTX;
		else PORTB = PORTB | RXTX;
		replyBuf[0] = PINB & CWKEY;     // read SDA and cw key level simultaneously
        return 1;
    }
	if(rq->bRequest == 0x51){       	// read SDA and cw key level simultaneously
		replyBuf[0] = PINB & CWKEY;
        return 1;
    }
	replyBuf[0] = 0xff;					// return value 0xff => command not supported 
    return 1;
}

/* ------------------------------------------------------------------------- */
/* ------------------------ Oscillator Calibration ------------------------- */
/* ------------------------------------------------------------------------- */

/* Calibrate the RC oscillator to 8.25 MHz. The core clock of 16.5 MHz is
 * derived from the 66 MHz peripheral clock by dividing. Our timing reference
 * is the Start Of Frame signal (a single SE0 bit) available immediately after
 * a USB RESET. We first do a binary search for the OSCCAL value and then
 * optimize this value with a neighboorhod search.
 * This algorithm may also be used to calibrate the RC oscillator directly to
 * 12 MHz (no PLL involved, can therefore be used on almost ALL AVRs), but this
 * is wide outside the spec for the OSCCAL value and the required precision for
 * the 12 MHz clock! Use the RC oscillator calibrated to 12 MHz for
 * experimental purposes only!
 */
static void calibrateOscillator(void)
{
uchar       step = 128;
uchar       trialValue = 0, optimumValue;
int         x, optimumDev, targetValue = (unsigned)(1499 * (double)F_CPU / 10.5e6 + 0.5);

    /* do a binary search: */
    do{
        OSCCAL = trialValue + step;
        x = usbMeasureFrameLength();    /* proportional to current real frequency */
        if(x < targetValue)             /* frequency still too low */
            trialValue += step;
        step >>= 1;
    }while(step > 0);
    /* We have a precision of +/- 1 for optimum OSCCAL here */
    /* now do a neighborhood search for optimum value */
    optimumValue = trialValue;
    optimumDev = x; /* this is certainly far away from optimum */
    for(OSCCAL = trialValue - 1; OSCCAL <= trialValue + 1; OSCCAL++){
        x = usbMeasureFrameLength() - targetValue;
        if(x < 0)
            x = -x;
        if(x < optimumDev){
            optimumDev = x;
            optimumValue = OSCCAL;
        }
    }
    OSCCAL = optimumValue;
}
/*
Note: This calibration algorithm may try OSCCAL values of up to 192 even if
the optimum value is far below 192. It may therefore exceed the allowed clock
frequency of the CPU in low voltage designs!
You may replace this search algorithm with any other algorithm you like if
you have additional constraints such as a maximum CPU clock.
For version 5.x RC oscillators (those with a split range of 2x128 steps, e.g.
ATTiny25, ATTiny45, ATTiny85), it may be useful to search for the optimum in
both regions.
*/

void    usbEventResetReady(void)
{
    calibrateOscillator();
    eeprom_write_byte(0, OSCCAL);   /* store the calibrated value in EEPROM */
}

/* ------------------------------------------------------------------------- */
/* --------------------------------- main ---------------------------------- */
/* ------------------------------------------------------------------------- */

int main(void)
{
uchar   i;
//////////////////////////////////////////////////////////////////////////////////////////////

	fcryst=114.285*_2(19)*_2(5); 							//initialize fcryst to crystal frequency[MHz]*2^24
	if (eeprom_read_byte((uint8_t *)F_CAL_STATUS)!=0xff) 	//  read calibrated crystal frequency if available
		{
		for (i=0;i<4;i++)
			{
			fcr.bytes[i]=eeprom_read_byte((uint8_t *)(i+F_CRYST)); //read f_cryst from EEPROM if available
			}
		}
//	crystal frequency set
///////////////////////////////////////////////////////////////////////////////////////////////
//  Load frequency preset if available
    I2C_adr = eeprom_read_byte((uint8_t *)INIT_SI570); //  read init status = I2C address for SI570
	if (I2C_adr!=0xff) 	
		{
		for (i=0;i<6;i++)
			{
			SI570_data[i]=eeprom_read_byte((uint8_t *)(i+INIT_FREQ)); //read init frequency from EEPROM if available
			}
		SI570_Load();
		}
//  Frequency preset done
///////////////////////////////////////////////////////////////////////////////////////////////
uchar   calibrationValue;
//  usbConnect sequence
    calibrationValue = eeprom_read_byte((uint8_t *)RC_CAL_VALUE); /* calibration value from last time */
    if(calibrationValue != 0xff){
        OSCCAL = calibrationValue;
    }
    usbDeviceDisconnect();
    for(i=0;i<60;i++){  // 900 ms disconnect 
        _delay_ms(15);
    }
    usbDeviceConnect();
//  usbConnect sequence done
	DDRB = RXTX;				//all port pins inputs except RXTX switching output
	PORTB = 0;				    //RX on on startup, no pullups
    wdt_enable(WDTO_120MS );	//watchdog 1s
    usbInit();
    sei();
    for(;;)
		{    /* main event loop */
        wdt_reset();
        usbPoll();
    	}
    return 0;
}
