//==========================================================================
//
//      lm3s_flash.c
//
//      LM3S internal flash driver
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 2008, 2009 Free Software Foundation, Inc.                        
//
// 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
// Date:         2009-07-30
//
//####DESCRIPTIONEND####
//
//========================================================================*/

#include <pkgconf/hal_cortexm_lm3s.h>
#include <pkgconf/devs_flash_lm3s.h>

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

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

#include <cyg/hal/hal_arch.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/hal/hal_cache.h>
#include <cyg/hal/hal_io.h>
#include <cyg/hal/hal_if.h>

#include <string.h>

#include <cyg/io/lm3s_flash.h>

#include CYGHWR_MEMORY_LAYOUT_H

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

typedef cyg_uint32 LM3S_TYPE;

# define LM3S_INTSCACHE_STATE     int _saved_ints_
# define LM3S_INTSCACHE_BEGIN()   HAL_DISABLE_INTERRUPTS(_saved_ints_)
# define LM3S_INTSCACHE_SUSPEND() HAL_RESTORE_INTERRUPTS(_saved_ints_)
# define LM3S_INTSCACHE_RESUME()  HAL_DISABLE_INTERRUPTS(_saved_ints_)
# define LM3S_INTSCACHE_END()     HAL_RESTORE_INTERRUPTS(_saved_ints_)

#define LM3S_UNCACHED_ADDRESS(__x) ((LM3S_TYPE *)(__x))

// ----------------------------------------------------------------------------
// Forward declarations for functions that need to be placed in RAM:

static int lm3s_flash_hw_erase(cyg_flashaddr_t addr) __attribute__((section (".2ram.lm3s_flash_hw_erase")));
static int lm3s_flash_hw_program( volatile LM3S_TYPE* addr, const cyg_uint32* buf, cyg_uint32 count) __attribute__((section (".2ram.lm3s_flash_hw_program")));
    
// ----------------------------------------------------------------------------
// Diagnostic routines.

#if 0
#define lmf_diag( __fmt, ... ) diag_printf("LMF: %20s[%3d]: " __fmt, __FUNCTION__, __LINE__, ## __VA_ARGS__ );
#define lmf_dump_buf( __addr, __size ) diag_dump_buf( __addr, __size )
#else
#define lmf_diag( __fmt, ... )
#define lmf_dump_buf( __addr, __size )
#endif

// ----------------------------------------------------------------------------
// Initialize the flash.


static int
lm3s_flash_init(struct cyg_flash_dev* dev)
{
    cyg_lm3s_flash_dev *lm3s_dev = (cyg_lm3s_flash_dev *)dev->priv;
    cyg_uint32 dc0;
    cyg_uint32 flash_size, block_size = 0;
    extern cyg_uint32 hal_lm3s_sysclk;

    
    // Set up the block info entries.

    dev->block_info                             = &lm3s_dev->block_info[0];
    dev->num_block_infos                        = 1;

    // Get flash size from device capability register
    HAL_READ_UINT32( CYGHWR_HAL_LM3S_SYSTEM+CYGHWR_HAL_LM3S_SYSTEM_DC0, dc0 );

    lmf_diag("dc0 %08x\n", dc0 );
    
    flash_size = 1024*2*(CYGHWR_HAL_LM3S_SYSTEM_DC0_FLASHSZ(dc0)+1);
    block_size = 1024;
    
    lm3s_dev->block_info[0].blocks             = flash_size/block_size;
    lm3s_dev->block_info[0].block_size         = block_size;
    
    // Set end address
    dev->end                                    = dev->start+flash_size-1;

    lmf_diag("block_size %d size %08x end %08x\n", block_size, flash_size, dev->end );    

    // Set Flash timing register to sysclk frequency minus 1 in MHz.

    lmf_diag("USECRL %d\n", (hal_lm3s_sysclk/1000000)-1 );
    HAL_WRITE_UINT32( CYGHWR_HAL_LM3S_SYSTEM+CYGHWR_HAL_LM3S_FLASH_USECRL, (hal_lm3s_sysclk/1000000)-1 );
    
    return CYG_FLASH_ERR_OK;
}

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

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

// ----------------------------------------------------------------------------
// Get info about the current block, i.e. base and size.

static void
lm3s_flash_get_block_info(struct cyg_flash_dev* dev, const cyg_flashaddr_t addr, cyg_flashaddr_t* block_start, size_t* block_size)
{
    size_t          offset  = addr - dev->start;
    
    *block_start    = dev->start + (offset & (dev->block_info[0].block_size-1));
    *block_size     = dev->block_info[0].block_size;
}

// ----------------------------------------------------------------------------
// Erase a single sector. There is no API support for chip-erase. The
// generic code operates one sector at a time, invoking the driver for
// each sector, so there is no opportunity inside the driver for
// erasing multiple sectors in a single call. The address argument
// points at the start of the sector.

static int
lm3s_flash_hw_erase(cyg_flashaddr_t addr)
{
    cyg_uint32 base = CYGHWR_HAL_LM3S_FLASH;
    cyg_uint32 fmc;
    cyg_uint32 timeout = 100000;

    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_FLASH_FMA, addr );
    
    fmc = CYGHWR_HAL_LM3S_FLASH_FMC_ERASE | CYGHWR_HAL_LM3S_FLASH_FMC_KEY;
    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_FLASH_FMC, fmc );

    do
    {
        HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_FLASH_FMC, fmc );
    } while( (fmc & CYGHWR_HAL_LM3S_FLASH_FMC_ERASE) && timeout-- > 0);

    if( timeout == 0 )
        return CYG_FLASH_ERR_ERASE;

    return CYG_FLASH_ERR_OK;
}

// ----------------------------------------------------------------------------
// Write data to flash, using individual word writes. The destination
// address will be aligned in a way suitable for the bus. The source
// address need not be aligned. The count is in LM3S_TYPE's, not in
// bytes.

static int
lm3s_flash_hw_program( volatile LM3S_TYPE* addr, const cyg_uint32* buf, cyg_uint32 count)
{
    cyg_uint32 base = CYGHWR_HAL_LM3S_FLASH;
    cyg_uint32 fmc;

    while( count-- )
    {
        cyg_uint32 timeout = 100000;
        
        HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_FLASH_FMD, *buf );
        HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_FLASH_FMA, (cyg_uint32)addr );
    
        fmc = CYGHWR_HAL_LM3S_FLASH_FMC_WRITE | CYGHWR_HAL_LM3S_FLASH_FMC_KEY;
        HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_FLASH_FMC, fmc );
    
        do
        {
            HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_FLASH_FMC, fmc );
        } while( (fmc & CYGHWR_HAL_LM3S_FLASH_FMC_WRITE) && timeout-- > 0);

        if( timeout == 0 )
            return CYG_FLASH_ERR_PROGRAM;
        
        addr++;
        buf++;
    }

    return CYG_FLASH_ERR_OK;
}

// ----------------------------------------------------------------------------
// Erase a single block. The calling code will have supplied a pointer
// aligned to a block boundary.

static int
lm3s_flash_erase(struct cyg_flash_dev* dev, cyg_flashaddr_t dest)
{
    int                     (*erase_fn)(cyg_uint32);
    volatile LM3S_TYPE*    uncached;
    cyg_flashaddr_t         block_start;
    size_t                  block_size;
    int                     result;
    LM3S_INTSCACHE_STATE;

    lmf_diag("dest %p\n", dest);
    CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required");
    CYG_ASSERT((dest >= dev->start) && (dest <= dev->end), "flash address out of device range");

    lm3s_flash_get_block_info(dev, dest, &block_start, &block_size);
    CYG_ASSERT(dest == block_start, "erase address should be the start of a flash block");

    uncached    = LM3S_UNCACHED_ADDRESS(dest);
    erase_fn    = (int (*)(cyg_uint32)) cyg_flash_anonymizer( & lm3s_flash_hw_erase );

    LM3S_INTSCACHE_BEGIN();    

    result = (*erase_fn)(uncached);
    
    LM3S_INTSCACHE_END();
    
    return result;
}

// ----------------------------------------------------------------------------
// Write some data to the flash. The destination must be aligned to a
// 32 bit boundary. Higher level code guarantees that the data will
// not straddle a block boundary.

int
lm3s_flash_program(struct cyg_flash_dev* dev, cyg_flashaddr_t dest, const void* src, size_t len)
{
    int                     (*program_fn)(volatile LM3S_TYPE*, const cyg_uint32*, cyg_uint32);
    volatile LM3S_TYPE*    uncached; 
    const cyg_uint32*       data;
    size_t                  to_write;
    int                     result  = CYG_FLASH_ERR_OK;

    LM3S_INTSCACHE_STATE;

    lmf_diag("dest %p src %p len %p(%d)\n", dest, src, len, len);
    CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required");
    CYG_ASSERT((dest >= dev->start) && ((CYG_ADDRESS)dest <= dev->end), "flash address out of device range");

    // Source and destination must be 32-bit aligned.
    if( (0 != ((CYG_ADDRESS)dest & 3)) ||
        (0 != ((CYG_ADDRESS)src  & 3)) )
        return CYG_FLASH_ERR_INVALID;
    
    uncached    = LM3S_UNCACHED_ADDRESS(dest);
    data        = (const cyg_uint32*) src;
    to_write    = len / sizeof(LM3S_TYPE);      // Number of words, not bytes
    program_fn  = (int (*)(volatile LM3S_TYPE*, const cyg_uint32*, cyg_uint32)) cyg_flash_anonymizer( & lm3s_flash_hw_program );

    LM3S_INTSCACHE_BEGIN();
    while (to_write > 0)
    {
        size_t this_write = (to_write < CYGNUM_DEVS_FLASH_LM3S_V2_PROGRAM_BURST_SIZE) ?
                             to_write : CYGNUM_DEVS_FLASH_LM3S_V2_PROGRAM_BURST_SIZE;

        
        result = (*program_fn)(uncached, data, this_write);
        if (result != CYG_FLASH_ERR_OK)
        {
            break;
        }
        to_write -= this_write;
        if (to_write > 0)
        {
            // There is still more to be written. The last write must have been a burst size
            uncached    += this_write;
            data        += this_write;
            LM3S_INTSCACHE_SUSPEND();
            LM3S_INTSCACHE_RESUME();
        }
    }
    LM3S_INTSCACHE_END();
    return result;
}

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

const CYG_FLASH_FUNS(cyg_lm3s_flash_funs,
                     &lm3s_flash_init,
                     &lm3s_flash_query,
                     &lm3s_flash_erase,
                     &lm3s_flash_program,
                     (int (*)(struct cyg_flash_dev*, const cyg_flashaddr_t, void*, size_t))0,
                     cyg_flash_devfn_lock_nop,
                     cyg_flash_devfn_unlock_nop);

// ----------------------------------------------------------------------------
// End of lm3s_flash.c
