/* uart.c */
/*
    This file is part of the Crypto-avr-lib/microcrypt-lib.
    Copyright (C) 2008  Daniel Otte (daniel.otte@rub.de)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/* USART-Init beim ATmegaXX */

#include "config.h"

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h>

#include "uart.h"

#ifdef ATMEGA128
#define UCSRB UCSR0B
#define UCSRC UCSR0C
#define UDR UDR0
#define UBRRH UBRR0H
#define UBRRL UBRR0L
#define URSEL UMSEL
#endif

#ifdef ATMEGA644
#define UCSRB UCSR0B
#define UCSRC UCSR0C
#define UDR UDR0
#define UBRRH UBRR0H
#define UBRRL UBRR0L
#define URSEL UMSEL00
#define USART_UDRE_vect USART0_UDRE_vect
#define USART_RXC_vect USART0_RX_vect
#define UDRIE UDRIE0
#define TXEN TXEN0
#define UMSEL UMSEL0
#define RXEN RXEN0
#define RXCIE RXCIE0
#define UCSZ0 UCSZ00
#define UCSRA UCSR0A
#define UDRE UDRE0
#define RXC RXC0
#endif


#ifdef UART_XON_XOFF
 #ifdef UART_INTERRUPT
  void uart_insertc(char c);
 #else
  #define uart_insertc uart_putc
 #endif /* UART_INTERRUPT */
#endif

#define UART_BAUD_CALC(UART_BAUD_RATE,F_OSC) ((F_OSC)/((UART_BAUD_RATE)*16L)-1)

#ifdef UART_XON_XOFF
	typedef enum{go=1,nogo=0} gonogo;
	static gonogo txon=go;
	static gonogo rxon=go;
#endif

#ifdef UART_INTERRUPT
volatile static char rxbuf[UART_RXBUFSIZE];
volatile static char txbuf[UART_TXBUFSIZE];
volatile static char *volatile rxhead, *volatile rxtail;
volatile static char *volatile txhead, *volatile txtail;

#ifdef UART_HOOK	
	 void (*uart_hook) (uint8_t) = (void*)0;	/* this is a pointer to a function ;-) */
#endif

ISR(USART_UDRE_vect) {
#ifdef UART_LEDS	
	PORTC ^= 0x01;
#endif
	
	if ( txhead == txtail ) {
		UCSRB &= ~(1 << UDRIE);		/* disable data register empty IRQ */
	} else {
		#ifdef UART_XON_XOFF
			while(txon==nogo)
				;
		#endif
		UDR = *txtail;			/* schreibt das Zeichen x auf die Schnittstelle */
		if (++txtail == (txbuf + UART_TXBUFSIZE)) txtail = txbuf;
	}
}

ISR(USART_RXC_vect) {
	int diff; 
	char c;
#ifdef UART_HOOK	
	static volatile uint8_t hook_running=0;
#endif
#ifdef UART_LEDS
	PORTC ^= 0x02;
#endif
	c=UDR;
	#ifdef UART_XON_XOFF
	 if (c==XON){
		  txon=go;
		  return;
	 }
	 if (c==XOFF){
		 txon=nogo;
		 return;
	 }
	#endif
	/* buffer full? */
	diff = rxhead - rxtail;
	if (diff < 0) diff += UART_RXBUFSIZE; /* diff is the amount of bytes in buffer */
	if (diff < UART_RXBUFSIZE -1) {
		// buffer NOT full
#ifdef UART_HOOK
		if(!hook_running && uart_hook){
			uint8_t t=c;	
			hook_running = 1;
			sei(); /* reenable interrupts, avoid recursion!!! */
			do {
				uart_hook(t);
			} while(uart_getc_nb((char*)&t));
			hook_running = 0;
		} else {
			*rxhead = c;
			++rxhead;
			if (rxhead == (rxbuf + UART_RXBUFSIZE)) rxhead = rxbuf;
		}
#else	
		*rxhead = c;
		++rxhead;
		if (rxhead == (rxbuf + UART_RXBUFSIZE))
			rxhead = rxbuf;
#endif
	} else {
						//reads the buffer to clear the interrupt condition
	}
#ifdef UART_XON_XOFF	
	if((diff > UART_XON_XOFF_THRESHOLD_1) && (rxon==go)){
		rxon=nogo;
		uart_insertc(XOFF);
	}
	if((diff < UART_XON_XOFF_THRESHOLD_2) && (rxon==nogo)){
		rxon=go;
		uart_insertc(XON);
	}
#endif
}

#endif // UART_INTERRUPT


void uart_init() {
	PORTD |= 0x01;				//Pullup an RXD an
	
	UCSRB |= (1<<TXEN);			//UART TX einschalten
#ifdef ATMEGA644
	UCSRA = 0;
	UCSRC = (3<<UCSZ0);		    //Asynchron 8N1
#else	
    UCSRA = 0;
	UCSRC |= (1<<URSEL)|(3<<UCSZ0);		//Asynchron 8N1
#endif
	UCSRB |= ( 1 << RXEN );			//Uart RX einschalten

	UBRRH=(uint8_t)(UART_BAUD_CALC(UART_BAUD_RATE,F_CPU)>>8);
	UBRRL=(uint8_t)(UART_BAUD_CALC(UART_BAUD_RATE,F_CPU));

#ifdef UART_INTERRUPT
	// init buffers
	rxhead = rxtail = rxbuf;
	txhead = txtail = txbuf;

	// activate rx IRQ
	UCSRB |= _BV(RXCIE) | _BV(UDRIE);
	sei();
//	#ifdef ATMEGA644
//	UCSRB |= _BV(UDRIE);
//	#endif
#endif // UART_INTERRUPT
}

#ifdef UART_INTERRUPT
#ifdef UART_XON_XOFF

void uart_insertc(char c){
	volatile int diff;
	do {
		diff = txhead - txtail;
		if ( diff < 0 ) diff += UART_TXBUFSIZE;
	} while ( diff >= UART_TXBUFSIZE -1 );

	cli();
 	if (--txtail == (txbuf-1)) txtail += UART_TXBUFSIZE;
	*txtail = c;
	
	UCSRB |= (1 << UDRIE);		/* enable data register empty IRQ */
	sei();
}
#endif /* UART_XON_XOFF */
void uart_putc(char c) {
	volatile int diff;

	/* buffer full? */
	do {
		diff = txhead - txtail;
		if ( diff < 0 ) diff += UART_TXBUFSIZE;
	} while ( diff >= UART_TXBUFSIZE -1 );

	cli();
	*txhead = c;
 	if (++txhead == (txbuf + UART_TXBUFSIZE)) txhead = txbuf;

	UCSRB |= (1 << UDRIE);		/* enable data register empty IRQ */
	sei();
}
#else  // WITHOUT INTERRUPT
void uart_putc(char c) {
	while (!(UCSRA & (1<<UDRE))) /* warten bis Senden moeglich */
		; 
	#ifdef UART_XON_XOFF
	while (txon==nogo) 			 /* warte bis XON empfangen */
		;
	#endif
	UDR = c;                  /* schreibt das Zeichen x auf die Schnittstelle */
}
#endif // UART_INTERRUPT


void uart_putstr(char *str) {
	while(*str) {
		uart_putc(*str++);
	}
}

void uart_putstr_P(PGM_P str) {
	char tmp;
	while((tmp = pgm_read_byte(str))) {
		uart_putc(tmp);
		str++;
	}
}

void uart_hexdump(const void* buf, int len)
{
	unsigned char table[]={'0','1','2','3','4','5','6','7',
		                   '8','9','a','b','c','d','e','f'};
	while(len--){
		uart_putc(table[((*((char*)buf))>>4)&0xf]);
		uart_putc(table[(*((char*)buf))&0xf]);
		uart_putc(' ');
		buf=(uint8_t*)buf+1;
	}
}


#ifdef UART_INTERRUPT
char uart_getc(void)
{
	char val;

	while(rxhead==rxtail)
		;

	val = *rxtail;
 	++rxtail;
 	if (rxtail == (rxbuf + UART_RXBUFSIZE))
 		rxtail = rxbuf;

	return val;
}
#else  // WITHOUT INTERRUPT
char uart_getc(void)
{
	char t;
	while (!(UCSRA & (1<<RXC)))
		;	// warten bis Zeichen verfuegbar
	t=UDR;
	#ifdef UART_XON_XOFF
	if (t==XON)  txon=go;
	if (t==XOFF) txon=nogo;
	#endif
	return t;			// Zeichen aus UDR zurueckgeben
}
#endif // UART_INTERRUPT

// returns 1 on success
#ifdef UART_INTERRUPT
char uart_getc_nb(char *c)
{
	if (rxhead==rxtail) return 0;

	*c = *rxtail;
 	if (++rxtail == (rxbuf + UART_RXBUFSIZE)) rxtail = rxbuf;

	return 1;
}
#else  // WITHOUT INTERRUPT
char uart_getc_nb(char *c)
{
	if (UCSRA & (1<<RXC)) {		// Zeichen verfuegbar
		*c = UDR;
		#ifdef UART_XON_XOFF
			if (*c==XON)  txon=go;
			if (*c==XOFF) txon=nogo;
		#endif
		return 1;
	}

	return 0;
}
#endif // UART_INTERRUPT
