// ==============================================================================
// midi_gnusb.c
// globals and utilities for gnusbCore - OPEN SOURCE USB SENSOR BOX
//
// License:
// The project is built with AVR USB driver by Objective Development, which is
// published under an own licence based on the GNU General Public License (GPL).
// usb2dmx is also distributed under this enhanced licence. See Documentation.
//
// target-cpu: ATMega16 @ 12MHz
// created 2007-01-28 Michael Egger me@anyma.ch
//

// ==============================================================================
// includes
// ------------------------------------------------------------------------------
// AVR Libc (see http://www.nongnu.org/avr-libc/)
#include <avr/io.h>			// include I/O definitions (port names, pin names, etc)
#include <avr/pgmspace.h> 	
#include <avr/wdt.h>		// include watchdog timer support
#include <avr/interrupt.h>
#include <util/delay.h>
// USB driver by Objective Development (see http://www.obdev.at/products/avrusb/index.html)
#include "usbdrv.h"

// local includes
#include "midi_gnusb.h"		// gnusb setup and utility functions 


// ------------------------------------------------------------------------------
// - Status Leds
// ------------------------------------------------------------------------------
// 							(on means  set to 0 as we sink the LEDs )
 
void statusLedOn(StatusLeds led) 		{STATUS_LED_PORT &= ~(1 << led); }
void statusLedOff(StatusLeds led) 		{STATUS_LED_PORT |= (1 << led);}
void statusLedToggle(StatusLeds led)	{STATUS_LED_PORT ^= 1 << led;}

// ------------------------------------------------------------------------------
// - ADC Utilities
// ------------------------------------------------------------------------------
void adInit(void){
	// --------------------- Init AD Converter

	ADCSRA |= (1 << ADEN);				// enable ADC (turn on ADC power)
	ADCSRA &= ~(1 << ADATE);			// default to single sample convert mode
										// Set ADC-Prescaler (-> precision vs. speed)

	ADCSRA = ((ADCSRA & ~ADC_PRESCALE_MASK) | ADC_PRESCALE_DIV64); // Set ADC Reference Voltage to AVCC
	ADMUX |= (1 << REFS0);
	ADMUX &= ~(1 << REFS1);
	
	ADCSRA &= ~(1 << ADLAR);				// set to right-adjusted result//	sbi(ADCSRA, ADIE);				// enable ADC interrupts
	ADCSRA &= ~(1 << ADIE);				// disable ADC interrupts
	adStartConversion();
}

int adConversionComplete (void) {
	return (!(ADCSRA & (1 << ADSC)));
}

 int adRead10bit (void) {
 	return (ADCL | ADCH << 8);
 }
 
 int adRead8bit (void) {
 	return adRead10bit() >> 2;
 }
 
 void adSetChannel (unsigned char mux) {
 	ADMUX = (ADMUX & ~ADC_MUX_MASK) | (mux & ADC_MUX_MASK);		// set channel
 }
 
 void adStartConversion (void) {
 			ADCSRA |= (1 << ADIF);			// clear hardware "conversion complete" flag 
			ADCSRA |= (1 << ADSC);			// start conversion
}


/*---------------------------------------------------------------------------*/
/* SEND MIDI                                                           */ 
/*---------------------------------------------------------------------------*/

void sendControlChange(unsigned char controller, unsigned char value) {
		midiMsg[0] = 0x0b;
		midiMsg[1] = 0xb0;
		midiMsg[2] = (controller & 0x7F);		//  MIDI is 7bit
		midiMsg[3] = (value & 0x7F);			//  MIDI is 7bit
		usbSetInterrupt(midiMsg, 4);
}

void sendNote(unsigned char key, unsigned char velocity) {
		midiMsg[0] = 0x09;
		midiMsg[1] = 0x90;
		midiMsg[2] = (key & 0x7F);		//  MIDI is 7bit
		midiMsg[3] = (velocity & 0x7F);			//  MIDI is 7bit
		usbSetInterrupt(midiMsg, 4);
}

// ------------------------------------------------------------------------------
// - usbFunctionDescriptor
// ------------------------------------------------------------------------------

uchar usbFunctionDescriptor(usbRequest_t * rq)
{

	if (rq->wValue.bytes[1] == USBDESCR_DEVICE) {
		usbMsgPtr = (uchar *) deviceDescrMIDI;
		return sizeof(deviceDescrMIDI);
	} else {		/* must be config descriptor */
		usbMsgPtr = (uchar *) configDescrMIDI;
		return sizeof(configDescrMIDI);
	}
}


// ------------------------------------------------------------------------------
// - usbFunctionSetup
// ------------------------------------------------------------------------------
// this function gets called when the usb driver receives a non standard request
// that is: our own requests defined in ../common/gnusb_cmds.h
uchar usbFunctionSetup(uchar data[8])
{
	usbRequest_t *rq = (void *) data;


	if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {	/* class request type */

		/*  Prepare bulk-in endpoint to respond to early termination   */
		if ((rq->bmRequestType & USBRQ_DIR_MASK) ==
		    USBRQ_DIR_HOST_TO_DEVICE) {}
	}
	switch (data[1]) {
// 								----------------------------   Start Bootloader for reprogramming the gnusb    		
		case GNUSBCORE_CMD_START_BOOTLOADER:

			startBootloader();
			break;
				
		default:
			break;
	}
	return 0xff;
}


// ---------------------------------------------------------------------------
//  usbFunctionRead                                                          
// ---------------------------------------------------------------------------

uchar usbFunctionRead(uchar * data, uchar len)
{

//	statusLedToggle(StatusLed_Yellow);

//???? thats from http://cryptomys.de/horo/V-USB-MIDI/index.html
	data[0] = 0;
	data[1] = 0;
	data[2] = 0;
	data[3] = 0;
	data[4] = 0;
	data[5] = 0;
	data[6] = 0;

	return 7;
}


/*---------------------------------------------------------------------------*/
/* usbFunctionWrite                                                          */
/*---------------------------------------------------------------------------*/

uchar usbFunctionWrite(uchar * data, uchar len)
{
	// DEBUG LED
//	statusLedToggle(StatusLed_Yellow);
	return 1;
}

/*---------------------------------------------------------------------------*/
/* usbFunctionWriteOut                                                       */
/*                                                                           */
/* this Function is called if a MIDI Out message (from PC) arrives.          */
/*                                                                           */
/*---------------------------------------------------------------------------*/

void usbFunctionWriteOut(uchar * data, uchar len)
{
	statusLedToggle(StatusLed_Yellow);

	while (len >= sizeof(midi_msg)) {
		midi_msg* msg = (midi_msg*)data;
		
		if (msg->byte[0] == 0x90) {
			if (msg->byte[2]) {  handleNoteOn(msg->byte[1]); }	// velocity > 0
			else {			handleNoteOff(msg->byte[1]);}		// velocity = 0 -> Note Off

		} else if (msg->byte[0] == 0x80) {
				handleNoteOff(msg->byte[1]);
		}
		data += sizeof(midi_msg);
		len -= sizeof(midi_msg);
	}
}


// ------------------------------------------------------------------------------
// - Enumerate device
// ------------------------------------------------------------------------------

static void initForUsbConnectivity(void)
{
uchar   i = 0;

    usbInit();
    /* enforce USB re-enumerate: */
    usbDeviceDisconnect();  /* do this while interrupts are disabled */
    while(--i){         /* fake USB disconnect for > 250 ms */
        wdt_reset();
        _delay_ms(1);
    }
    usbDeviceConnect();
    sei();
}


// ==============================================================================
// - Init hardware
// ------------------------------------------------------------------------------
void initMidiGnusb(void)
{

wdt_enable(WDTO_1S);	// enable watchdog timer

	// --------------------- Init USB
/* from usbrdv.h	******************* We exepct that the PORT and DDR bits for D+ and D- have
 * not been changed from their default status (which is 0). If you have changed
 * them, set both back to 0 (configure them as input with no internal pull-up).
 */
	// set PORT D Directions -> 1110 0000, output 0 on unconnected PD7
	DDRD = 0xe0; 	// 1110 0000 -> set PD0..PD4 to inputs -> USB pins
	PORTD = 0x70; 	// set Pullup for Bootloader Jumper, no pullups on USB pins -> 0111 0000

initForUsbConnectivity();
// TODO: this seems like complete bullshit
// shouldnt we disonnect, wait, connect, enable wdt after that?
//	usbDeviceConnect();
//	usbReset();
  //  usbInit();

	sei();		// turn on interrupts

}

// ------------------------------------------------------------------------------
// - Start Bootloader
// ------------------------------------------------------------------------------
// dummy function doing the jump to bootloader section (Adress 1C00 on Atmega16)
void (*jumpToBootloader)(void) = (void (*)(void))0x1C00; __attribute__ ((unused))

void startBootloader(void) {
		
		
		MCUCSR &= ~(1 << PORF);			// clear power on reset flag
										// this will hint the bootloader that it was forced
	
		cli();							// turn off interrupts
		wdt_disable();					// disable watchdog timer
		usbDeviceDisconnect(); 			// disconnect gnusb from USB bus
		
		ADCSRA &= ~(( 1 << ADIE) | ( 1 << ADEN));	// disable ADC interrupts
													// disable ADC (turn off ADC power)

		statusLedOff(StatusLed_Yellow);		
		statusLedOff(StatusLed_Green);

		jumpToBootloader();
}

