//==========================================================================
//
//      at91_flash.c
//
//      Flash programming for the at91 devices which have the 
//      Embedded Flash Controller.
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2006, 2007 Free Software Foundation, Inc.
// Copyright (C) 2006, 2007 eCosCentric Limited                             
//
// eCos 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 2 or (at your option) any later      
// version.                                                                 
//
// eCos 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 eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    nickg
// Contributors: gthomas, dmoseley, Andrew Lunn, Oliver Munz
// Date:         2007-10-12
// Purpose:      
// Description:  
//              
//####DESCRIPTIONEND####
//
//==========================================================================

#include <pkgconf/hal.h>
#include <pkgconf/devs_flash_at91.h>

#include <cyg/infra/cyg_type.h>
#include <cyg/infra/cyg_ass.h>
#include <cyg/infra/diag.h>

#include <cyg/hal/hal_io.h>             
#include <cyg/hal/hal_intr.h>

#include <cyg/io/flash.h>
#include <cyg/io/flash_dev.h>

#include <string.h>

#define FLASH_TIMEOUT       100000

#ifdef CYGBLD_DEV_FLASH_AT91_LOCKING
static cyg_uint32 sector_size;
#endif

// ----------------------------------------------------------------------------
// The driver-specific data, pointed at by the priv field in a
// a cyg_flash_dev structure.

typedef struct cyg_at91_dev
{
    // Space for the block_info fields needed for the cyg_flash_dev.
    // These are initialized dynamically during initialization.
    cyg_flash_block_info_t  block_info[1];
    
} cyg_at91_dev;

// ----------------------------------------------------------------------------
// Diagnostic routines.

#if 0
#define af_diag( __fmt, ... ) diag_printf("AF: %30s[%d]: " __fmt, __FUNCTION__, __LINE__, ## __VA_ARGS__ );
#define af_dump_buf( __addr, __size ) diag_dump_buf( __addr, __size )
#else
#define af_diag( __fmt, ... )
#define af_dump_buf( __addr, __size )
#endif

// ----------------------------------------------------------------------------
// Disable the flash controller from erasing the page before
// programming it

static void 
flash_erase_before_write_disable ( CYG_ADDRESS efc )
{
    cyg_uint32 fmr;
  
    HAL_READ_UINT32(efc+AT91_MC_FMR, fmr);
    fmr = fmr | AT91_MC_FMR_NEBP;
    HAL_WRITE_UINT32(efc+AT91_MC_FMR, fmr);
}

// ----------------------------------------------------------------------------
// Enable the flash controller to erase the page before programming
// it

static void 
flash_erase_before_write_enable ( CYG_ADDRESS efc )
{
    cyg_uint32 fmr;

    af_diag("efc %08x\n", efc);
    
    HAL_READ_UINT32(efc+AT91_MC_FMR, fmr);
    fmr = fmr & ~((cyg_uint32) AT91_MC_FMR_NEBP);
    HAL_WRITE_UINT32(efc+AT91_MC_FMR, fmr);
}

// ----------------------------------------------------------------------------
// Is the flash controller ready to accept the next command?

static __inline__ cyg_bool 
flash_controller_is_ready( CYG_ADDRESS efc )
CYGBLD_ATTRIB_SECTION(".2ram.flash_run_command");

static __inline__ cyg_bool 
flash_controller_is_ready( CYG_ADDRESS efc )
{
    cyg_uint32 fsr;
  
    HAL_READ_UINT32(efc+AT91_MC_FSR, fsr);
    return (fsr & AT91_MC_FSR_FRDY ? true : false);
}

// ----------------------------------------------------------------------------
// Busy loop waiting for the controller to finish the command.
// Wait a maximum of timeout loops and then return an error.

static __inline__ int 
flash_wait_for_controller (CYG_ADDRESS efc, cyg_uint32 timeout) 
CYGBLD_ATTRIB_SECTION(".2ram.flash_run_command");

static __inline__ int 
flash_wait_for_controller (CYG_ADDRESS efc, cyg_uint32 timeout)
{
    while (!flash_controller_is_ready(efc)){
        timeout--;
        if (!timeout) {
            return CYG_FLASH_ERR_DRV_TIMEOUT;
        }
    }
    return CYG_FLASH_ERR_OK;
}

// ----------------------------------------------------------------------------
// Calculate the flash controller to use.
//
// This assumes that each controller handles 1024 pages. If this is
// not true for some future controller, then this code will need to be
// parameterized.

#define flash_efc_address(__page) (AT91_MC+AT91_MC_EFC0+AT91_MC_EFCN((__page)>>10))

// ----------------------------------------------------------------------------
// Execute one command on the flash controller. This code should
// probably not be in flash

static int 
flash_run_command(struct cyg_flash_dev* dev,
                  cyg_uint32 address, 
                  cyg_uint32 command, 
                  cyg_uint32 timeout) 
CYGBLD_ATTRIB_SECTION(".2ram.flash_run_command");

static int 
flash_run_command(struct cyg_flash_dev* dev,
                  cyg_uint32 address, 
                  cyg_uint32 command, 
                  cyg_uint32 timeout) 
{
    cyg_uint32 retcode;
    cyg_uint32 fsr;
    cyg_uint32 mask;
    cyg_uint32 page;
    CYG_ADDRESS efc;

    page = ((cyg_uint32) address - (cyg_uint32) dev->start) / 
        dev->block_info[0].block_size;

    // Calculate the flash controller to use and the page within it.
    efc = flash_efc_address(page);
//    page = page&0x00000CFF;

    af_diag("addr %x cmd %x timeout %d efc %08x page %08x\n", address, command, timeout, efc, page );
        
    // Wait for the last command to finish
    retcode = flash_wait_for_controller(efc, timeout);
    if (retcode != CYG_FLASH_ERR_OK){
        return retcode;
    }
  
    HAL_DISABLE_INTERRUPTS(mask);
  
    HAL_WRITE_UINT32(efc+AT91_MC_FCR, 
                     command |
                     ((page & AT91_MC_FCR_PAGE_MASK) << AT91_MC_FCR_PAGE_SHIFT) |
                     AT91_MC_FCR_KEY);

    retcode = flash_wait_for_controller(efc, timeout);

    HAL_RESTORE_INTERRUPTS(mask);

    if (retcode != CYG_FLASH_ERR_OK){
        return retcode;
    }

    // Check for an error
    HAL_READ_UINT32(efc+AT91_MC_FSR, fsr);

    if ((fsr & AT91_MC_FSR_LOCKE) == AT91_MC_FSR_LOCKE)
        return CYG_FLASH_ERR_PROTECT;
    if ((fsr & AT91_MC_FSR_PROGE) == AT91_MC_FSR_PROGE)
        return CYG_FLASH_ERR_PROGRAM;

    return CYG_FLASH_ERR_OK;
}

// ----------------------------------------------------------------------------

static size_t
flash_at91_query(struct cyg_flash_dev* dev, void* data, size_t len)
{
    static char query[] = "AT91SAM7 Internal Flash";
    memcpy( data, query, sizeof(query));
    return sizeof(query);
}

// ----------------------------------------------------------------------------
// Initialize the hardware. Make sure we have a flash device we know
// how to program and determine its size, the size of the blocks, and
// the number of blocks. The query function returns the chip ID 1
// register which tells us about the CPU we are running on, the flash
// size etc. Use this information to determine we have a valid setup.

static int 
flash_at91_init(struct cyg_flash_dev* dev)
{
    cyg_at91_dev *at91_dev = (cyg_at91_dev*) dev->priv;

    cyg_uint32 chipID1r;
    cyg_uint32 flash_mode;
    cyg_uint8  fmcn;
    cyg_uint32 lock_bits;
    cyg_uint32 page;
    
    af_diag("");

    HAL_READ_UINT32(AT91_DEBUG+AT91_DBG_C1R, chipID1r);
    
    if ((chipID1r & AT91_DBG_C1R_CPU_MASK) != AT91_DBG_C1R_ARM7TDMI)
        goto out;

    if (((chipID1r & AT91_DBG_C1R_ARCH_MASK) != AT91_DBG_C1R_ARCH_AT91SAM7Sxx) &&
        ((chipID1r & AT91_DBG_C1R_ARCH_MASK) != AT91_DBG_C1R_ARCH_AT91SAM7Xxx) &&
        ((chipID1r & AT91_DBG_C1R_ARCH_MASK) != AT91_DBG_C1R_ARCH_AT91SAM7XC) &&
        ((chipID1r & AT91_DBG_C1R_ARCH_MASK) != AT91_DBG_C1R_ARCH_AT91SAM7SExx))
        goto out;
  
    if ((chipID1r & AT91_DBG_C1R_FLASH_MASK) == AT91_DBG_C1R_FLASH_0K)
        goto out;
  
    if ((chipID1r & AT91_DBG_C1R_NVPTYP_MASK) != AT91_DBG_C1R_NVPTYP_ROMFLASH)
    {
        switch (chipID1r & AT91_DBG_C1R_FLASH_MASK) {
        case AT91_DBG_C1R_FLASH_32K:
            at91_dev->block_info[0].block_size      = 128;
            at91_dev->block_info[0].blocks          = 256;
            lock_bits = 8;
            break;
        case AT91_DBG_C1R_FLASH_64K:
            at91_dev->block_info[0].block_size      = 128;
            at91_dev->block_info[0].blocks          = 512;
            lock_bits = 16;
            break;
        case AT91_DBG_C1R_FLASH_128K:
            at91_dev->block_info[0].block_size      = 256;
            at91_dev->block_info[0].blocks          = 256;
            lock_bits = 8;
            break;
        case AT91_DBG_C1R_FLASH_256K:
            at91_dev->block_info[0].block_size      = 256;
            at91_dev->block_info[0].blocks          = 1024;
            lock_bits = 16;
            break;
#ifdef AT91_MC_FMR1
        case AT91_DBG_C1R_FLASH_512K:
            at91_dev->block_info[0].block_size      = 256;
            at91_dev->block_info[0].blocks          = 2048;
            lock_bits = 32;
            break;
#endif
        default:
            goto out;
        }
    } else {
        // if there is both flash & ROM then: ROM=AT91_DBG_C1R_FLASH, flash=AT91_DBG_C1R_FLASH2
        switch (chipID1r & AT91_DBG_C1R_FLASH2_MASK) {
        case AT91_DBG_C1R_FLASH2_32K:
            at91_dev->block_info[0].block_size = 128;
            at91_dev->block_info[0].blocks = 256;
            lock_bits = 8;
            break;
        case AT91_DBG_C1R_FLASH2_64K:
            at91_dev->block_info[0].block_size = 128;
            at91_dev->block_info[0].blocks = 512;
            lock_bits = 16;
            break;
        case AT91_DBG_C1R_FLASH2_128K:
            at91_dev->block_info[0].block_size = 256;
            at91_dev->block_info[0].blocks = 512;
            lock_bits = 8;
            break;
        case AT91_DBG_C1R_FLASH2_256K:
            at91_dev->block_info[0].block_size = 256;
            at91_dev->block_info[0].blocks = 1024;
            lock_bits = 16;
            break;
#ifdef AT91_MC_FMR1
        case AT91_DBG_C1R_FLASH2_512K:
            at91_dev->block_info[0].block_size = 256;
            at91_dev->block_info[0].blocks = 2048;
            lock_bits = 32;
            break;
#endif
        default:
            goto out;
        }
    }
    dev->num_block_infos = 1;
    dev->start = 0x00100000;
    dev->end = (((cyg_uint32) dev->start) + 
                dev->block_info[0].block_size * dev->block_info[0].blocks) - 1;
#ifdef CYGBLD_DEV_FLASH_AT91_LOCKING
    sector_size = dev->block_size * dev->blocks / lock_bits;
#endif

    // Set the FLASH clock to 1.5 microseconds based on the MCLK.  This
    // assumes the CPU is still running from the PLL clock as defined in
    // the HAL CDL and the HAL startup code. 
    fmcn = CYGNUM_HAL_ARM_AT91_CLOCK_SPEED * 1.5 / 1000000 + 0.999999; // We must round up!
    HAL_READ_UINT32(AT91_MC+AT91_MC_EFC0+AT91_MC_FMR, flash_mode);
    flash_mode = flash_mode & ~AT91_MC_FMR_FMCN_MASK;
    flash_mode = flash_mode | (fmcn << AT91_MC_FMR_FMCN_SHIFT);
    
    for( page = 0; page < at91_dev->block_info[0].blocks; page += 1024 )
    {
        CYG_ADDRESS efc = flash_efc_address(page);

        af_diag("init EFC %08x fmr %08x\n", efc, flash_mode);
        
        HAL_WRITE_UINT32(efc+AT91_MC_FMR, flash_mode);
    }
  
    return CYG_FLASH_ERR_OK;
  
out:
    (*dev->pf)("Can't identify FLASH, sorry, ChipID1 %x\n",
               chipID1r );
    return CYG_FLASH_ERR_HWR;
}

// ----------------------------------------------------------------------------
// Erase a block. The flash controller does not have a command to
// erase a block. So instead we setup the controller to do a program
// writing all 0xff with an erase operation first.

static int 
flash_at91_erase (struct cyg_flash_dev* dev, cyg_flashaddr_t dest)
{
//    cyg_at91_dev *at91_dev = (cyg_at91_dev*) dev->priv;
    cyg_uint32 retcode;
    cyg_uint32 *buffer;
    cyg_uint32 *end;
    cyg_uint32 page;
        
    af_diag("dest %x\n", dest);

    buffer = (cyg_uint32 *) dest;
    end = (cyg_uint32 *) (dest + dev->block_info[0].block_size);
  
    while (buffer < end)
    { 
        *buffer = (cyg_uint32) 0xffffffff;
        buffer++;
    }

    page = ((cyg_uint32) dest - (cyg_uint32) dev->start) / 
        dev->block_info[0].block_size;
    
    flash_erase_before_write_enable(flash_efc_address(page));
    retcode = flash_run_command(dev, dest, 
                                AT91_MC_FCR_START_PROG, 
                                FLASH_TIMEOUT);
  
    return retcode;
}

// ----------------------------------------------------------------------------
// Write into the flash. The datasheet says that performing 8 or 16bit
// accesses results in unpredictable corruption. So the current code
// checks that these conditions are upheld. It would be possible to
// perform extra reads and masking operation to support writing to
// none word assigned addresses or not multiple or a word length.

static int 
flash_at91_program (struct cyg_flash_dev* dev, cyg_flashaddr_t dest, const void* src, size_t len)
{
//    cyg_at91_dev *at91_dev = (cyg_at91_dev*) dev->priv;
    cyg_uint32 retcode;
    cyg_uint32 page;
    volatile unsigned long *target;
    const volatile unsigned long *data = src;

    af_diag("dest %x src %p len %ld\n", dest, src, len);
    
    CYG_ASSERT(len % 4 == 0, "Only word writes allowed by current code");
    CYG_ASSERT(dest % 4 == 0, "Address must be word aligned for current code");
  
    target = (volatile unsigned long *)dest;
  
    while (len > 0)
    {
        *target = *data;
        data++;
        target++;
        len = len - sizeof(unsigned long);
    }

    page = ((cyg_uint32) dest - (cyg_uint32) dev->start) / 
        dev->block_info[0].block_size;
    
    flash_erase_before_write_disable(flash_efc_address(page));
    retcode = flash_run_command(dev, dest, 
                                AT91_MC_FCR_START_PROG, 
                                FLASH_TIMEOUT);
  
    return retcode;
}

#ifdef CYGBLD_DEV_FLASH_AT91_LOCKING

// ----------------------------------------------------------------------------
// Unlock a block. This is not strictly possible, we can only lock and
// unlock sectors. This will unlock the sector which contains the
// block.

static int
flash_at91_unlock(struct cyg_flash_dev* dev, const cyg_flashaddr_t dest)
{
    cyg_at91_dev *at91_dev = (cyg_at91_dev*) dev->priv;    
    cyg_uint32 sector;
    cyg_uint32 retcode;
    cyg_uint32 status;

    af_diag("dest %p\n", dest);
    
    sector = (((cyg_uint32) dest) - (cyg_uint32) dev->start) / 
        sector_size;
 
    HAL_READ_UINT32(efc + AT91_MC_FSR, status);
  
    if (status & (1 << (sector + 16)))
    {
        retcode = flash_run_command(dev, dest, 
                                    AT91_MC_FCR_UNLOCK, 
                                    FLASH_TIMEOUT);
        return retcode;
    }

    return CYG_FLASH_ERR_OK;
}

// ----------------------------------------------------------------------------
// Lock a block. This is not strictly possible, we can only lock and
// unlock sectors. This will lock the sector which contains the
// block.

static int
flash_at91_lock(struct cyg_flash_dev* dev, const cyg_flashaddr_t dest)
{
    cyg_uint32 sector;
    cyg_uint32 retcode;
    cyg_uint32 status;

    af_diag("dest %p\n", dest);
    
    sector = (((cyg_uint32) dest) - (cyg_uint32) dev->start) / 
        sector_size;

    HAL_READ_UINT32(efc + AT91_MC_FSR, status);
  
    if (!(status & (1 << (sector + 16))))
    {
        retcode = flash_run_command(dev, dest, 
                                    AT91_MC_FCR_LOCK, 
                                    FLASH_TIMEOUT);
      
        return retcode;
    }

    return CYG_FLASH_ERR_OK;

}
#endif 

// ----------------------------------------------------------------------------
// Function table

const CYG_FLASH_FUNS(cyg_at91_flash_funs,
                     &flash_at91_init,
                     &flash_at91_query,
                     &flash_at91_erase,
                     &flash_at91_program,
                     (int (*)(struct cyg_flash_dev*, const cyg_flashaddr_t, void*, size_t))0,
#ifdef CYGBLD_DEV_FLASH_AT91_LOCKING                     
                     &flash_at91_lock,
                     &flash_at91_unlock
#else
                     (int (*)(struct cyg_flash_dev* , const cyg_flashaddr_t))0,
                     (int (*)(struct cyg_flash_dev* , const cyg_flashaddr_t))0
#endif
    );

static cyg_at91_dev hal_str710eval_at91_flash_priv;

CYG_FLASH_DRIVER(hal_str710eval_flash_internal,
                 &cyg_at91_flash_funs,
                 0,
                 (0x00100000),
                 0,
                 0,
                 hal_str710eval_at91_flash_priv.block_info,
                 &hal_str710eval_at91_flash_priv
);

// ----------------------------------------------------------------------------
