/******************************************************************************
 * Flash File System (ffs)
 * Idea, design and coding by Mads Meisner-Jensen, mmj@ti.com
 *
 * FFS AMD single bank low level flash driver RAM code
 *
 * $Id: amdsbdrv.c 1.5.1.3 Tue, 06 Jan 2004 10:57:45 +0100 tsj $
 *
 ******************************************************************************/

#include "ffs.cfg"

#include "ffs/ffs.h"
#include "ffs/board/drv.h"
#include "ffs/board/ffstrace.h"


// Due to long branches, we disable all tracing and led function calls.
#undef  tlw
#define tlw(contents)
#undef  ttw
#define ttw(contents)


asm("        .label _ffsdrv_ram_amd_begin");
asm("        .def   _ffsdrv_ram_amd_begin");


// IMPORTANT! Apparently, placing the int_disable/enable() function code
// here instead of at the bottom of the file, makes the code crash or
// freeze. Reason is as of yet unknown.

uint32 amd_int_disable(void);
void amd_int_enable(uint32 tmp);


/******************************************************************************
 * AMD Single Bank Driver Functions
 ******************************************************************************/

void ffsdrv_ram_amd_sb_write_halfword(volatile uint16 *addr, uint16 value)
{
    volatile char *flash = dev.base;
    uint32 cpsr;

    ttw(ttr(TTrDrv, "wh(%x,%x)" NL, addr, value));

    if (~*addr & value) {
        ttw(ttr(TTrFatal, "wh(%x,%x->%x) fatal" NL, addr, *addr, value));
        return;
    }

    cpsr = amd_int_disable();
    tlw(led_on(LED_WRITE));

    flash[0xAAAA] = 0xAA; // AMD unlock cycle 1
    flash[0x5555] = 0x55; // AMD unlock cycle 2
    flash[0xAAAA] = 0xA0;
    *addr         = value;

    while ((*addr ^ value) & 0x80)
	;

    tlw(led_off(LED_WRITE));
    amd_int_enable(cpsr);
}

// This VERY simple way of erase suspension only works because we run under
// a pre-emptive operating system, so whenever an interrupt occurs, another
// task takes the CPU, and at the end of the interrupt, FFS gets the CPU
// again.
void ffsdrv_ram_amd_sb_erase(uint8 block)
{
    volatile char *flash = dev.base;
    volatile char *addr;
    uint32 cpsr;
    uint16 flashpoll;

    addr = block2addr(block);

    ttw(ttr(TTrDrvEra, "e(%d)" NL, block));

    cpsr = amd_int_disable();
    tlw(led_on(LED_ERASE));

    flash[0xAAAA] = 0xAA; // AMD unlock cycle 1
    flash[0x5555] = 0x55; // AMD unlock cycle 2
    flash[0xAAAA] = 0x80; 
    flash[0xAAAA] = 0xAA; // AMD unlock cycle 1
    flash[0x5555] = 0x55; // AMD unlock cycle 2
    *addr         = 0x30; // AMD erase sector command

    // Wait for erase to finish.
    while ((*addr & 0x80) == 0) {
        tlw(led_toggle(LED_ERASE));
        // Poll interrupts, taking interrupt mask into account.
        if (INT_REQUESTED)
        {
            // 1. suspend erase
            // 2. enable interrupts
            // .. now the interrupt code executes
            // 3. disable interrupts
            // 4. resume erase

            tlw(led_on(LED_ERASE_SUSPEND));
            *addr = 0xB0;

            // wait for erase suspend to finish
            while ((*addr & 0x80) == 0)
                ;

            tlw(led_off(LED_ERASE_SUSPEND));
            amd_int_enable(cpsr);

            // Other interrupts and tasks run now...

            cpsr = amd_int_disable();
            tlw(led_on(LED_ERASE_SUSPEND));

            // Before resuming erase we must? check if the erase is really
            // suspended or if it did finish
            flashpoll = *addr;
            *addr = 0x30;

            tlw(led_off(LED_ERASE_SUSPEND));
        }
    }

    tlw(led_on(LED_ERASE));
    tlw(led_off(LED_ERASE));
    amd_int_enable(cpsr);
}


/******************************************************************************
 * Interrupt Enable/Disable
 ******************************************************************************/

uint32 amd_int_disable(void)
{
    asm("        .state16");
    asm("        mov       A1, #0xC0");
    asm("        ldr       A2, tct_amd_disable");
    asm("        bx        A2      ");

    asm("tct_amd_disable 	.field     _TCT_Control_Interrupts+0,32");
    asm("	                .global	   _TCT_Control_Interrupts");
}

void amd_int_enable(uint32 cpsr)
{
    asm("        .state16");
    asm("        ldr       A2, tct_amd_enable");
    asm("        bx        A2      ");

    asm("tct_amd_enable 	.field     _TCT_Control_Interrupts+0,32");
    asm("	                .global	   _TCT_Control_Interrupts");
}

// Even though we have this end label, we cannot determine the number of
// constant/PC-relative data following the code!
asm("        .state32");
asm("        .label _ffsdrv_ram_amd_end");
asm("        .def   _ffsdrv_ram_amd_end");
