/*- Flash Memory Read, Write, Erase Operations
 *
 *  Copyright (C) 1993-1995 MCT Paul & Scherer, Berlin.
 *  All rights reserved.
 *
 *  Permission to use this software and its documentation 
 *  is hereby granted, provided that the above copyright 
 *  notice appear in all copies and that both the copyright 
 *  notice and warranty disclaimer appear in supporting 
 *  documentation, and that the names of MCT Paul & Scherer 
 *  GmbH and any of their entities not be used in advertising 
 *  or publicity pertaining to distribution of the software 
 *  without specific, written prior permission.
 * 
 *  MCT disclaims all warranties with regard to this software, 
 *  including all implied warranties of merchantability and 
 *  fitness.  In no event shall MCT be liable for any special, 
 *  indirect or consequential damages or any damages whatsoever 
 *  resulting from loss of use, data or profits, whether in an 
 *  action of contract, negligence or other tortuous action, 
 *  arising out of or in connection with the use or performance 
 *  of this software.
 */

/*- NOTES
 *
 * - All block transfers will be rounded up to full fwords. 
 *   This means you cannot write a lone byte into 16bit flash 
 *   memory.
 */
 
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <target.h>
#include <sys/nofloat.h>

#include "flash.h"

#ifndef KILO
    #define KILO        *1024
    #define MEGA        *1024 KILO
    #define GIGA        *1024 MEGA
#endif

#define dimof(v) (sizeof(v) / sizeof(*(v)))

#if defined(mega332)
    #define FLASH_ON    (INTERN.sim.csorbt |=  0x00001000)
    #define FLASH_OFF   (INTERN.sim.csorbt &= ~0x00001000)
#elif defined(mega340)
    #define FLASH_ON    (INTERN.sim.cs[0].base &= ~0x00000008)
    #define FLASH_OFF   (INTERN.sim.cs[0].base |=  0x00000008)
#else
    #define FLASH_ON    
    #define FLASH_OFF   
#endif

static  char    fbuffer[512];

struct typtab{
    char *manufacturer;
    char *name;
    long id;
    long size;
    long block_size;
}typtab[] = {
    {"Unknown",     "Unknown",      -1,     0, 0},
    {"AMD",         "AM29F010",     0x0120, 128 KILO, 16 KILO},
    {"AMD",         "AM29F040",     0x01a4, 512 KILO, 64 KILO},
    {0}
};

        long flash_size = 128 * 1024;
        long flash_block_size = 128 * 1024 / 8 * sizeof(fword);
static  struct typtab *flash_type = &typtab[0];
static  int lastid0, lastid1, lastid2;

int
set_flash_size(long id)
{
    struct typtab *p;

    for(p = typtab; p->manufacturer; p++){
        if(id == p->id){
            flash_size = p->size;
            flash_block_size = p->block_size * sizeof(fword);
            flash_type = p;
            return 0;
        }
    }
    flash_size = 128 * 1024;
    flash_block_size = 128 * 1024 / 8 * sizeof(fword);
    flash_type = &typtab[0];
    return FLASH_EUNKNOWN;
}

/*- Identify flash memory.
 *
 *  NOTE: This currently supports 8, 16 and 32 Bit flash 
 *        memory blocks only. All flash memories must be 
 *        of the same type or a failure will be reported.
 *
 *  Returns the identifier as a 16 Bit number. Failure to 
 *  get consistent identity information will be signalled 
 *  with a negative return value.
 */

long
flash_identify(volatile void *flash_memory)
{
    static int second;
    static fword id0, id1, id2;
    int rv;
    
    if(!second){
        second = 1;
        #ifdef DEBUG
            printf("flash_identify(%p)\n", flash_memory);
        #endif
        memcpy(fbuffer, flash_identify, dimof(fbuffer));
        IRQ_OFF; 
        rv = set_flash_size( ((long (*)(volatile void *))
         fbuffer)(flash_memory) );
        IRQ_ON;
        lastid0 = id0;
        lastid1 = id1;
        lastid2 = id2;
        return rv;
    }
    second = 0;
    FLASH_ON;

    FLASH_A2F(flash_memory)[0x5555] = 0xAAAAAAAA;
    FLASH_A2F(flash_memory)[0x2AAA] = 0x55555555;
    FLASH_A2F(flash_memory)[0x5555] = 0x90909090;
    
    id0 = FLASH_A2F(flash_memory)[0x0000];
    id1 = FLASH_A2F(flash_memory)[0x0001];
    id2 = FLASH_A2F(flash_memory)[0x0002];

    FLASH_A2F(flash_memory)[0x5555] = 0xAAAAAAAA;
    FLASH_A2F(flash_memory)[0x2AAA] = 0x55555555;
    FLASH_A2F(flash_memory)[0x5555] = 0xF0F0F0F0;

    FLASH_OFF;

    if( (sizeof(fword) >= 2 && (id0 & 0xff) != ((id0 >> 8) & 0xff))
     || (sizeof(fword) == 4 && (id0&0xffff) != ((id0 >>16)&0xffff))
     || (sizeof(fword) >= 2 && (id1 & 0xff) != ((id1 >> 8) & 0xff))
     || (sizeof(fword) == 4 && (id1&0xffff) != ((id1 >>16)&0xffff))
    ){
        return FLASH_EMIX;
    }
    return ((id0 & 0xff) << 8) | (id1 & 0xff);
}

/*- Return name of manufacturer and chip.
 *
 *  Note: flash_identify() must be called first!
 */

char *
flash_manufacturer(void)
{
    static char buf[16];

    if(flash_type == typtab){
        sprintf(buf, "0x%X", lastid0);
        return buf;
    }
    return flash_type->manufacturer;
}

char *
flash_name(void)
{
    static char buf[16];

    if(flash_type == typtab){
        sprintf(buf, "0x%X", lastid1);
        return buf;
    }
    return flash_type->name;
}

/*- READ
 *
 * Read buffer_size bytes to buffer from flash_memory 
 * and return how many were read or the negative error code.
 */
 
int
flash_read(volatile void *flash_memory, void *buffer, long buffer_size)
{
    long n;
    
    #ifdef DEBUG
        printf("flash_read(%p, %p, %ld)\n",
         flash_memory, buffer, buffer_size);
    #endif
    for(n = 0; n * sizeof(fword) < buffer_size; n++){
        ((fword *)buffer)[n] = ((fword *)flash_memory)[n];
    }
    return n * sizeof(fword);
}

/*- VERIFY
 *
 * Compare buffer_size bytes from buffer with flash_memory 
 * and return how many were read or the negative error code.
 */
 
int
flash_verify(void *flash_memory, void *buffer, long buffer_size)
{
    long    n;

    #ifdef DEBUG
        printf("flash_verify(%p, %p, %ld)\n",
         flash_memory, buffer, buffer_size);
    #endif
    for(n = 0; n * sizeof(fword) < buffer_size; n++){
        if( ((fword *)buffer)[n] != ((fword *)flash_memory)[n]){
            return FLASH_EVERIFY;
        }
    }
    return n * sizeof(fword);
}

/*- WRITE
 *
 * Write buffer_size bytes from buffer to flash_memory 
 * and return how many were written or the negative error code.
 * WARNING: This assumes the block to write in was 
 *          erased with flash_*_erase().
 * If buffer_size is 0 nothing is written. This should be used 
 * to preload the function buffer.
 */

int
flash_write(volatile void *flash_memory, void *buffer, long buffer_size)
{
    long    n, tout;
    static int second;
    static char fwbuffer[512];
    static int preloaded;
    int rv;

    if(!second){
        second = 1;
        #ifdef DEBUG
            printf("flash_write(%p, %p, %ld)\n",
             flash_memory, buffer, buffer_size);
        #endif
        if(!preloaded){
            memcpy(fwbuffer, flash_write, dimof(fwbuffer));
            preloaded = 1;
        }
        IRQ_OFF; 
        rv = ((int (*)(volatile void *, void *, long))fwbuffer)
                (flash_memory, buffer, buffer_size);
        IRQ_ON;
        return rv;
    }
    second = 0;
    for(n = 0; (n + 1) * sizeof(fword) <= buffer_size; n++){
        if(((fword *)flash_memory)[n] != ((fword *)buffer)[n]){
            if(((fword *)flash_memory)[n] != (fword)0xFFFFFFFF){
                return FLASH_EWRITE;
            }
            FLASH_ON;
            FLASH_A2F(flash_memory)[0x5555] = 0xAAAAAAAA;
            FLASH_A2F(flash_memory)[0x2AAA] = 0x55555555;
            FLASH_A2F(flash_memory)[0x5555] = 0xA0A0A0A0;
            ((fword *)flash_memory)[n] = ((fword *)buffer)[n];
            FLASH_OFF;

            for(tout = 1000000;
             tout && ((fword *)flash_memory)[n] != ((fword *)buffer)[n];
             tout--);
            if(!tout){
                return FLASH_EWRITE;
            }
        }
    }
    return n * sizeof(fword);
}

/*- BLOCK ERASE
 *
 * Erase block pointed to by flash_memory.
 * Return 0 or the negative error code.
 */
 
int
flash_block_erase(void *flash_memory)
{
    long    tout;
    static int second;
    int rv;

    if(!second){
        #ifdef DEBUG
            printf("flash_block_erase(%p)\n", flash_memory);
        #endif
        second = 1;
        memcpy(fbuffer, flash_block_erase, dimof(fbuffer));
        IRQ_OFF;
        rv = ((int (*)(void *))fbuffer)(flash_memory);
        IRQ_ON;
        return rv;
    }
    second = 0;
    FLASH_ON;
    FLASH_A2F(flash_memory)[0x5555] = 0xAAAAAAAA;
    FLASH_A2F(flash_memory)[0x2AAA] = 0x55555555;
    FLASH_A2F(flash_memory)[0x5555] = 0x80808080;

    FLASH_A2F(flash_memory)[0x5555] = 0xAAAAAAAA;
    FLASH_A2F(flash_memory)[0x2AAA] = 0x55555555;
    *FLASH_A2B(flash_memory) = 0x30303030;
    FLASH_OFF;

    for(tout = 1000000;
     tout && *FLASH_A2B(flash_memory) != (fword)0xFFFFFFFF;
     tout--);
    if(!tout){
        return FLASH_EERASE;
    }
    return 0;
}
