/* sd2snes - SD card based universal cartridge for the SNES
   Copyright (C) 2009-2010 Maximilian Rehkopf <otakon@gmx.net>
   AVR firmware portion

   Inspired by and based on code from sd2iec, written by Ingo Korb et al.
   See sdcard.c|h, config.h.

   FAT file system access based on code by ChaN, Jim Brain, Ingo Korb,
   see ff.c|h.

   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; version 2 of the License only.

   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, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

   main.c: initialization and flow
*/

#include <stdio.h>
#include <string.h>
#include <avr/boot.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/power.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include "config.h"
#include "diskio.h"
#include "ff.h"
#include "led.h"
/* #include "timer.h" */
#include "fpga.h"
#include "uart.h"
#include "ustring.h"
#include "utils.h"
#include "snes.h"
#include "fileops.h"
#include "memory.h"
#include "fpga_spi.h"
#include "spi.h"
#include "avrcompat.h"
#include "filetypes.h"
#include "sdcard.h"

void writetest(void) {
// HERE BE LIONS, GET IN THE CAR
	char teststring[58];
	while(1) {
		sram_writeblock((void*)"Testtext of DOOM!!1! 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", SRAM_SCRATCHPAD+0x20, 58);
		sram_readblock((void*)teststring, SRAM_SCRATCHPAD+0x20, 58);
		teststring[57]=0;
		dprintf("%s\n", teststring);
	}
// END OF LIONS
}


void memtest(void) {
/* HERE BE DRAGONS */
	uint32_t dbg_i;
	for(dbg_i=0; dbg_i < 65536; dbg_i++) {
		sram_writeshort((uint16_t)dbg_i&0xffff, dbg_i*2);
	}
	save_sram((uint8_t*)"/sd2snes/memtest", 0x20000, 0);
	set_pwr_led(0);
	while(1);
/* END OF DRAGONS */
}


/* Make sure the watchdog is disabled as soon as possible    */
/* Copy this code to your bootloader if you use one and your */
/* MCU doesn't disable the WDT after reset!                  */
void get_mcusr(void) \
      __attribute__((naked)) \
      __attribute__((section(".init3")));
void get_mcusr(void)
{
  MCUSR = 0;
  wdt_disable();
}

#ifdef CONFIG_MEMPOISON
void poison_memory(void) \
  __attribute__((naked)) \
  __attribute__((section(".init1")));
void poison_memory(void) {
  register uint16_t i;
  register uint8_t  *ptr;

  asm("clr r1\n");
  /* There is no RAMSTARt variable =( */
  if (RAMEND > 2048 && RAMEND < 4096) {
    /* 2K memory */
    ptr = (void *)RAMEND-2047;
    for (i=0;i<2048;i++)
      ptr[i] = 0x55;
  } else if (RAMEND > 4096 && RAMEND < 8192) {
    /* 4K memory */
    ptr = (void *)RAMEND-4095;
    for (i=0;i<4096;i++)
      ptr[i] = 0x55;
  } else {
    /* Assume 8K memory */
    ptr = (void *)RAMEND-8191;
    for (i=0;i<8192;i++)
      ptr[i] = 0x55;
  }
}
#endif

#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1)
int main(void) __attribute__((OS_main));
#endif
int main(void) {
#if defined __AVR_ATmega644__ || defined __AVR_ATmega644P__ || defined __AVR_ATmega2561__
  asm volatile("in  r24, %0\n"
               "ori r24, 0x80\n"
               "out %0, r24\n"
               "out %0, r24\n"
               :
               : "I" (_SFR_IO_ADDR(MCUCR))
               : "r24"
               );
#elif defined __AVR_ATmega32__
  asm volatile ("in  r24, %0\n"
                "ori r24, 0x80\n"
                "out %0, r24\n"
                "out %0, r24\n"
                :
                : "I" (_SFR_IO_ADDR(MCUCSR))
                : "r24"
                );
#elif defined __AVR_ATmega128__ || defined __AVR_ATmega1281__
  /* Just assume that JTAG doesn't hurt us on the m128 */
#else
#  error Unknown chip!
#endif

#ifdef CLOCK_PRESCALE
	clock_prescale_set(CLOCK_PRESCALE);
#endif
	set_pwr_led(0);
	set_busy_led(1);
	spi_none();
	snes_reset(1);
	uart_init();
	sei();
	_delay_ms(100);
	disk_init();
	snes_init();
/*	timer_init(); */
	uart_puts_P(PSTR("\nsd2snes " VERSION));
	uart_putcrlf();

	file_init();
	FATFS fatfs;
	f_mount(0,&fatfs);
	uart_putc('W');
	fpga_init();
	fpga_pgm((uint8_t*)"/sd2snes/main.bit");
	_delay_ms(100);
	set_pwr_led(1);
	fpga_spi_init();
	uart_putc('!');
	_delay_ms(100);

restart:
	set_avr_ena(0);
	snes_reset(1);

	*fs_path=0;
	uint16_t saved_dir_id;
	get_db_id(&saved_dir_id);

	uint16_t mem_dir_id = sram_readshort(SRAM_DIRID);
	uint32_t mem_magic = sram_readlong(SRAM_SCRATCHPAD);

	while(0) {
		SD_SPI_OFFLOAD=0;
		set_avr_addr(0L);
		sd_read(0, file_buf, 0L, 1);
		uart_trace((void*)file_buf, 0, 0x200);
//		sram_writeblock((void*)file_buf, 0, 0x200);
//		sram_hexdump(0,0x200);
		uart_putc('+');
	}
/* here be strange monsters */
	while(0){
//	uint16_t hurdur1 = 0, hurdur2 = 0;
	spiTransferByte(0x00);
	spiTransferByte(0x00);
	spiTransferByte(0x00);
	spiTransferByte(0x00);
	spiTransferByte(0x00);
	spiTransferByte(0x00);
	spiTransferByte(0x00);
	spiTransferByte(0x00);
	spiTransferByte(0x00);
	spiTransferByte(0x00);
	spiTransferByte(0x00);
	spiTransferByte(0x00);
	spiTransferByte(0x00);
        PORTB |= _BV(PB2);
        DDRB |= _BV(PB2);
        PORTB &= ~_BV(PB2);
        DDRB &= ~_BV(PB7); // tristate SCK
        PORTB |= _BV(PB2);
        DDRB &= ~_BV(PB2);
        while(!(PINB & _BV(PB2))) {
        }
        DDRB |= _BV(PB7);
        _delay_ms(1);
//	dprintf("hurdur1=%d hurdur2=%d\n", hurdur1, hurdur2);
}
	if((mem_magic != 0x12345678) || (mem_dir_id != saved_dir_id)) {
		uint16_t curr_dir_id = scan_dir(fs_path, 0, 0); // generate files footprint
		dprintf("curr dir id = %x\n", curr_dir_id);
		if((get_db_id(&saved_dir_id) != FR_OK)	// no database?
		|| saved_dir_id != curr_dir_id) {	// files changed? // XXX
			dprintf("saved dir id = %x\n", saved_dir_id);
			dprintf("rebuilding database...");
			_delay_ms(50);
			curr_dir_id = scan_dir(fs_path, 1, 0);	// then rebuild database
			sram_writeblock(&curr_dir_id, SRAM_DB_ADDR, 2);
			uint32_t endaddr, direndaddr;
			sram_readblock(&endaddr, SRAM_DB_ADDR+4, 4);
			sram_readblock(&direndaddr, SRAM_DB_ADDR+8, 4);
			dprintf("%lx %lx\n", endaddr, direndaddr);
			save_sram((uint8_t*)"/sd2snes/sd2snes.db", endaddr-SRAM_DB_ADDR, SRAM_DB_ADDR);
			save_sram((uint8_t*)"/sd2snes/sd2snes.dir", direndaddr-(SRAM_DIR_ADDR), SRAM_DIR_ADDR);
			dprintf("done\n"); 
//			sram_hexdump(SRAM_DB_ADDR, 0x400);
		} else {
			dprintf("saved dir id = %x\n", saved_dir_id);
			dprintf("different card, consistent db, loading db...\n");
			load_sram((uint8_t*)"/sd2snes/sd2snes.db", SRAM_DB_ADDR);
			load_sram((uint8_t*)"/sd2snes/sd2snes.dir", SRAM_DIR_ADDR);
		}
//	save_sram((uint8_t*)"/debug.smc", 0x400000, 0);	
//	uart_putc('[');
//	load_sram((uint8_t*)"/test.srm", SRAM_SAVE_ADDR);
//	uart_putc(']');

		sram_writeshort(curr_dir_id, SRAM_DIRID);
		sram_writelong(0x12345678, SRAM_SCRATCHPAD);
	} else {
		dprintf("same card, loading db...\n");
		load_sram((uint8_t*)"/sd2snes/sd2snes.db", SRAM_DB_ADDR);
		load_sram((uint8_t*)"/sd2snes/sd2snes.dir", SRAM_DIR_ADDR);
	}

	led_pwm();

//	sram_hexdump(0, 0x200);
	uart_putc('(');
	load_rom((uint8_t*)"/sd2snes/menu.bin", SRAM_MENU_ADDR);
	set_rom_mask(0x3fffff); // force mirroring off
 	set_avr_mapper(0x7); // menu mapper XXX
	uart_putc(')');
	uart_putcrlf();
//	sram_hexdump(0x7ffff0, 0x10);
//	sram_hexdump(0, 0x400);
//	save_sram((uint8_t*)"/sd2snes/dump", 65536, 0);

	sram_writebyte(0, SRAM_CMD_ADDR);

	set_busy_led(0);
	set_avr_ena(1);

	_delay_ms(100);
	uart_puts_P(PSTR("SNES GO!\r\n"));
	snes_reset(0);
// writetest();
/*	snes_reset(1);
	set_avr_ena(0);
	led_std();
	set_busy_led(1);
	save_sram((uint8_t*)"/sd2snes/dump", 65536, SRAM_MENU_ADDR);
	set_busy_led(0);
	set_avr_ena(1);
	snes_reset(0); */
	uint8_t cmd = 0;

	while(!sram_reliable());
	while(!cmd) {
		cmd=menu_main_loop();
		switch(cmd) {
			case 0x01: // SNES_CMD_LOADROM:
				get_selected_name(file_lfn);
				_delay_ms(100);
//				snes_reset(1);
				set_avr_ena(0);
				dprintf("Selected name: %s\n", file_lfn);
				load_rom(file_lfn, SRAM_ROM_ADDR);
//				save_sram((uint8_t*)"/sd2snes/test.smc", romprops.romsize_bytes, 0);
				if(romprops.ramsize_bytes) {
					strcpy(strrchr((char*)file_lfn, (int)'.'), ".srm");
					dprintf("SRM file: %s\n", file_lfn);
					load_sram(file_lfn, SRAM_SAVE_ADDR);
				} else {
					dprintf("No SRAM\n");
				}
				set_avr_ena(1);
				snes_reset(1);
				_delay_ms(100);
				snes_reset(0);
				break;
			default:
				dprintf("unknown cmd: %d\n", cmd);
				cmd=0; // unknown cmd: stay in loop
			break;
		}
		
	}
	dprintf("cmd was %x, going to snes main loop\n", cmd);
	led_std();
	cmd=0;
	uint8_t snes_reset_prev=0, snes_reset_now=0, snes_reset_state=0;
	uint16_t reset_count=0;
	while(fpga_test() == FPGA_TEST_TOKEN) {
		snes_reset_now=get_snes_reset();
		if(snes_reset_now) {
			if(!snes_reset_prev) {
				dprintf("RESET BUTTON DOWN\n");
				snes_reset_state=1;
				// reset reset counter
				reset_count=0;
			}
		} else {
			if(snes_reset_prev) {
				dprintf("RESET BUTTON UP\n");
				snes_reset_state=0;
			}
		}
		if(snes_reset_state) {
			_delay_ms(10);
			reset_count++;
		} else {
			sram_reliable();
			snes_main_loop();
		}
		if(reset_count>100) {
			reset_count=0;
			led_std();
			set_avr_ena(0);
			snes_reset(1);
			_delay_ms(100);
			if(romprops.ramsize_bytes && fpga_test() == 0xa5) {
				set_busy_led(1);
				save_sram(file_lfn, romprops.ramsize_bytes, SRAM_SAVE_ADDR);
				set_busy_led(0);
			}
			_delay_ms(1000);
			set_busy_led(1);
			goto restart;
		}
		snes_reset_prev = snes_reset_now;
	}
	// FPGA TEST FAIL. PANIC.
	led_panic();

/* HERE BE LIONS */
while(1)  {	
	set_avr_addr(0x600000);
	spi_fpga();
	spiTransferByte(0x81); // read w/ increment... hopefully
	spiTransferByte(0x00); // 1 dummy read
	uart_putcrlf();
	uint8_t buff[21];
	for(uint8_t cnt=0; cnt<21; cnt++) {
		uint8_t data=spiTransferByte(0x00);
		buff[cnt]=data;
	}
	for(uint8_t cnt=0; cnt<21; cnt++) {
		uint8_t data = buff[cnt];
		_delay_ms(2);
		if(data>=0x20 && data <= 0x7a) {
			uart_putc(data);
		} else {
//			uart_putc('.');
			uart_putc("0123456789ABCDEF"[data>>4]);
			uart_putc("0123456789ABCDEF"[data&15]);
			uart_putc(' ');
		}
//		set_avr_bank(3);
	}
	spi_none();
}
		while(1);
}

