#include <avr/io.h>
#include <stdint.h>
#include <math.h>
#include <avr/interrupt.h>
#include <util/delay.h>


#include "main.h"


#include <spi.h>

/* SPI framework.
 *
 * currently alpha, uses interrupts, single master/single slave operation
 */

uint8_t readbuf[5];
uint8_t writebuf[5];
uint8_t pos;




void spi_init(){
	uint8_t spcr, spsr, d;

	/* calculate clock divisor,
	 * gcc with optimize will do that calculation compile-time. */
	uint8_t spi_clock_divisor(){
		double d; 
		d = F_CPU / SPI_BAUDRATE;
		d = ceil((log(d)/log(2))*0.95); // clock needs dividing by 2^d
										// the 0.95 to avoid ceil issues
		return d-1;  // the -1 because minimum divisor is /2
	}

	/* do pseudo-calculation in preprocessor, to get warnings 
	 * we right-shift 8 times; if result >0, d will be >7
	 * so output warning in that case
	 */
#define D_VAL (F_CPU / SPI_BAUDRATE)
#if ((D_VAL >> 8) > 0 )
#warning  "spi baudrate too slow, cannot be set (clamped to minimum)"
#endif

	d = spi_clock_divisor();
	if(d>7){
		// warning was (hopefully) output previously by cpp
		d=7;
	}

	//DDRs setzen
#ifdef SPI_MASTER
	DDRB |= _BV(5) | _BV(3) | _BV(2); //MOSI, SCK, unused SS
	DDRB &= ~( _BV(4)); //MISO
	SPI_SSDDR |= _BV(SPI_SS_PIN); // actually used SS
#else //SPI_MASTER
	DDRB |= _BV(4); //MISO
	DDRB &= ~( _BV(5) | _BV(3) | _BV(2)); //MOSI, SCK, SS
#endif //SPI_MASTER
	spsr = 0;
	spsr |=	(d & 1) ? 0 : _BV(SPI2X);
	spcr = 0 | _BV(SPE);
	spcr |= (d & 2) ? _BV(SPR0) : 0;
	spcr |= (d & 4) ? _BV(SPR1) : 0;

#ifdef SPI_MASTER
	spcr |= _BV(MSTR);
#endif		
	SPSR = spsr;
	SPCR = spcr;

#ifndef SPI_MASTER
    SPCR |= _BV(SPIE); //enable interrupts
    PCMSK0 |= _BV(2);
    PCICR |= _BV(0);
#endif
    pos = 0;
}



void spi_mst_start_packet(){
#ifdef SPI_MASTER
	SPI_SSOUT &= ~(_BV(SPI_SS_PIN));
	_delay_us(1e6/SPI_BAUDRATE);
#endif
}

void spi_mst_end_packet(){
#ifdef SPI_MASTER
	_delay_us(1e6/SPI_BAUDRATE);
	SPI_SSOUT |= _BV(SPI_SS_PIN);
	_delay_us(1e6/SPI_BAUDRATE);
#endif
}

#define SPI_WAIT while(!(SPSR & _BV(SPIF))){;}

void spi_mst_write(uint8_t len, uint8_t *data){
	while(len>0){
		SPDR = *data++;
		len--;
		SPI_WAIT;
	}
}

void spi_mst_read(uint8_t len, uint8_t *data){
	while(len>0){
		SPDR = 0;
		len--;
		SPI_WAIT;
		*data++ = SPDR;
	}
}

void spi_mst_write_read(uint8_t len, uint8_t *data){
	while(len>0){
		SPDR = *data;
		len--;
		SPI_WAIT;
		*data++ = SPDR;
	}
}

#ifndef SPI_MASTER

void spi_sla_handle_packet(){
        // TODO: make slave not hangup in case of partial read
	uint8_t opcode, addr;
	uint16_t data, newdata;
	writebuf[0] = 0; // generate invalid replies, so no inconsistent packets
						// are sent.
    opcode = readbuf[0];
    addr = readbuf[1];
	data = (readbuf[2]<<8) | readbuf[3];
	newdata = spi_proto_slaveaction(opcode, addr, data);
	writebuf[1]=addr;
	writebuf[2]=(newdata>>8)&0xff;
	writebuf[3]=(newdata)&0xff;
	writebuf[0]=opcode; // set opcode last to validate packet (it's a kind of locking)
}



ISR(SPI_STC_vect){
    SPDR = writebuf[pos];
    readbuf[pos] = SPDR;
    pos++;
    if(pos>=5){
        pos=4;
    }
}
	

	

ISR(PCINT0_vect){ // SS-line changed
    if(SPI_SSIN & _BV(SPI_SS_PIN)){ // SS-line was actually released
        pos=0;
        SPCR &= ~(_BV(SPIE));
        sei();
        spi_sla_handle_packet();
        SPCR |= _BV(SPIE);
    }
}



#endif //not SPI_MASTER




