#include <avr/io.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include "config.h"
#include "uart.h"
#include "spi.h"
#include "flash.h"

uint8_t
flash_init(void)
{
#ifdef FL_SEL_PORT
    TRACE("flash:init\n");
    FL_SEL_PORT |= 1<<FL_SEL_BIT;
    FL_SEL_DDR  |= 1<<FL_SEL_BIT;
    int8_t n = (flash_status() >> 2) & 0x0F;
    if (n == 3) return 1;	// AT45DB011B
    if (n == 7) return 2;	// AT45DB041B
    PRINTF("flash:devtype %d\n", n);
#else
    TRACE("flash:none\n");
#endif
    return 0;
}

uint16_t
flash_pages(void)
{
    int8_t n = (flash_status() >> 2) & 0x0F;
    if (n == 3) return 512;	// AT45DB011B
    if (n == 7) return 2048;	// AT45DB041B
    return 0;
}

static void
select()
{
#ifdef FL_SEL_PORT
    spi_init(3); // supports mode 0 and mode 3, but only compatible to AVR mode 3
    _delay_us(1);
    FL_SEL_PORT &= ~(1<<FL_SEL_BIT);
    _delay_us(1);
#endif
}

static void
deselect()
{
#ifdef FL_SEL_PORT
    _delay_us(1);
    FL_SEL_PORT |= (1<<FL_SEL_BIT);
    _delay_us(1);
#endif
}

/*-----------------------------------------------------------------------------*/

uint8_t
flash_status(void)
{
#ifdef FL_SEL_PORT
    select();
    spi(0x57);
    uint8_t r = spi(0);
    deselect();
    return r;
#else
    return 0;
#endif
}

void
flash_wait(void)
{
#ifdef FL_SEL_PORT
    while (!(flash_status() & 0x80))
	;
#endif
}

/*-----------------------------------------------------------------------------*/

void
flash_host2buf(uint8_t bufno, uint16_t offset, uint8_t *data, uint16_t len)
{
#ifdef FL_SEL_PORT
    if (len) {
	select();
	spi(bufno ? 0x87 : 0x84);
	spi(0);
	spi(offset >> 8);
	spi(offset);
	do spi(*data++); while (--len);
	deselect();
    }
#endif
}

void
flash_buf2mem(uint8_t bufno, uint16_t page)
{
#ifdef FL_SEL_PORT
    flash_wait();
    select();
    spi(bufno ? 0x86 : 0x83);
    spi(page >> 7);
    spi(spi(page << 1));
    deselect();
#endif
}
    
/*-----------------------------------------------------------------------------*/

void
flash_buf2host(uint8_t bufno, uint16_t offset, uint8_t *data, uint16_t len)
{
#ifdef FL_SEL_PORT
    if (len) {
	select();
	spi(bufno ? 0x56 : 0x54);
	spi(0);
	spi(offset >> 8);
	spi(spi(offset));
	do *data++ = spi(0); while (--len);
	deselect();
    }
#endif
}

void
flash_mem2host(uint16_t page, uint16_t offset, uint8_t *data, uint16_t len)
{
#ifdef FL_SEL_PORT
    if (len) {
	flash_wait();
	select();
	spi(0x52);
	spi(page >> 7);
	spi(page << 1 | offset >> 8);
	spi(spi(spi(spi(spi(offset)))));
	do *data++ = spi(0); while (--len);
	deselect();
    }
#endif
}

/*-----------------------------------------------------------------------------*/

void
flash_erase(void)
{
#ifdef FL_SEL_PORT
    uint16_t n = flash_pages() >> 3;
    for (uint16_t block = 0; block < n; ++block) {
	flash_wait();
	select();
	spi(0x50);
	spi(block >> 4);
	spi(spi(block << 4));
	deselect();

	wdt_reset();
    }
#endif
}
