//==========================================================================
//
//      at91rm9200_misc.c
//
//      HAL misc board support code for ARM9/AT91RM9200
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
// Copyright (C) 2003, 2004, 2005 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):    eCosCentric
// Contributors: hmt, Travis C. Furrer <furrer@mit.edu>, jskov
// Date:         2000-05-21
// Purpose:      HAL board support
// Description:  Implementations of HAL board interfaces
//
//####DESCRIPTIONEND####
//
//========================================================================*/

#include <pkgconf/hal.h>
#include <pkgconf/system.h>
#include CYGBLD_HAL_PLATFORM_H

#include <cyg/infra/cyg_type.h>         // base types
#include <cyg/infra/cyg_trac.h>         // tracing macros
#include <cyg/infra/cyg_ass.h>          // assertion macros

#include <cyg/hal/hal_io.h>             // IO macros
#include <cyg/hal/hal_arch.h>           // Register state info
#include <cyg/hal/hal_diag.h>
#include <cyg/hal/hal_intr.h>           // Interrupt names
#include <cyg/hal/hal_cache.h>
#include <cyg/hal/at91rm9200.h>         // Platform specifics

#include <cyg/infra/diag.h>             // diag_printf

//
// CPU specific initialization
//

__externC void hal_default_irq_vsr(void);
__externC void hal_default_fiq_vsr(void);

void
_at91rm9200_hardware_init(void)
{
    int i;

#ifndef CYGSEM_HAL_USE_ROM_MONITOR
    // Only do this if we are not running under a ROM monitor,
    // otherwise we are likely to mess up any interrupts that the
    // monitor has enabled, for example for Ctrl-C handling.
    HAL_WRITE_UINT32(_AIC_ICCR, 0xFFFFFFFF);  // Clear all interrupts
    HAL_WRITE_UINT32(_AIC_IDCR, 0xFFFFFFFF);  // Disable all interrupts

    // Clear system device interrupts

    HAL_WRITE_UINT32( _ST_IDR,  0xffffffff );
    HAL_WRITE_UINT32( _PMC_IDR, 0xffffffff );
    HAL_WRITE_UINT32( _RTC_IDR, 0xffffffff );
#endif

    // If using the AIC, reset all VSRs to our own default FIQ/IRQ VSRs
    // (potentially stealing them back from the ROM monitor in the process)
#if CYGINT_HAL_AT91RM9200_AIC_VSR
    HAL_WRITE_UINT32(_AIC_SVR, (CYG_ADDRESS)hal_default_fiq_vsr);  // FIQ vector
    // Reset all the IRQ vectors
    for (i = 1;  i < 32;  i++) {
        HAL_WRITE_UINT32(_AIC_SVR+(i*4), (CYG_ADDRESS)hal_default_irq_vsr);
#else
    // If not using the AIC we store the vector *number*, so that it can be retrieved more
    // easily in hal_IRQ_handler below.
    for (i = 0;  i < 32;  i++) {
        HAL_WRITE_UINT32(_AIC_SVR+(i*4), i);  // Vector
#endif
        HAL_WRITE_UINT32(_AIC_SMR+(i*4), 3);  // Priority. 0 == lowest, 7 == highest. Level-sensitive.
    }

#if defined(CYGSEM_HAL_VIRTUAL_VECTOR_CLAIM_DELAY_US) && defined(CYGPKG_KERNEL)
    // When this config is providing delay functionality, and the kernel is
    // included, then the kernel clock will be used. But use can happen before
    // the clock is initialised by the kernel. So we initialise it here.
    hal_clock_initialize( CYGNUM_HAL_RTC_PERIOD );
#endif

//    _csb337_i2c_init();

}

// -------------------------------------------------------------------------
void hal_clock_initialize(cyg_uint32 period)
{
    cyg_uint32 clock_mode;

    clock_mode = _TC_CMR_WAVESEL_UP_AUTO | _TC_CMR_WAVE | 3;  // Use timer_clock4 = MCK/128
    HAL_WRITE_UINT32(AT91RM9200_TC0+_TC_CMR, clock_mode);
    HAL_WRITE_UINT32(AT91RM9200_TC0+_TC_RC, period);
    HAL_WRITE_UINT32(AT91RM9200_TC0+_TC_CCR, _TC_CCR_CLKEN | _TC_CCR_SWTRG);
    HAL_WRITE_UINT32(AT91RM9200_TC0+_TC_IER, _TC_SR_CPCS);
}

// This routine is called during a clock interrupt.
void hal_clock_reset(cyg_uint32 vector, cyg_uint32 period)
{
    cyg_uint32 clock_stat;

    // Read status register to clear interrupt
    HAL_READ_UINT32(AT91RM9200_TC0+_TC_SR, clock_stat);
}

// Read the current value of the clock, returning the number of hardware
// "ticks" that have occurred (i.e. how far away the current value is from
// the start)

// Note: The "contract" for this function is that the value is the number
// of hardware clocks that have happened since the last interrupt (i.e.
// when it was reset).

void hal_clock_read(cyg_uint32 *pvalue)
{
    cyg_uint32 val;

    HAL_READ_UINT32(AT91RM9200_TC0+_TC_CV, val);
    *pvalue = val;
}

//
// Delay for some number of micro-seconds
//
// Use the system timer periodic timer (which is pretty useless 
// as the system heartbeat clock).  Since the current value of
// this can't be read, set it to run at max speed which corresponds
// to a frequency of 32768 Hz (30.5 us / tick)
//
void hal_delay_us(cyg_int32 usecs)
{
    unsigned long stat;
    int odd = 0;

    HAL_WRITE_UINT32(_ST_PIMR, 1);
    HAL_READ_UINT32(_ST_SR, stat);  // Clear interrupts 
    while (usecs > 0) {
        do {
            // Wait for the periodic timer interrupt
            HAL_READ_UINT32(_ST_SR, stat);
        } while ((stat & _ST_SR_PITS) == 0) ;
        usecs -= (30 + odd);
        odd ^= 1;  // Should be pretty accurate to 30.5
    }
}

// ----------------------------------------------------------------------------
// Profiling support. This uses TC1, set up in much the same way as
// the system timer, i.e. counting up from 0 to TC.

#ifdef CYGPKG_PROFILE_GPROF
#include <cyg/profile/profile.h>
#include <cyg/hal/drv_api.h>

static cyg_interrupt    profile_interrupt;
static cyg_handle_t     profile_handle;

static  void
profile_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data)
{
    CYG_UNUSED_PARAM(cyg_vector_t, vector);
    CYG_UNUSED_PARAM(cyg_ucount32, count);
    CYG_UNUSED_PARAM(cyg_addrword_t, data);
}

static cyg_uint32
profile_isr(cyg_vector_t vector, cyg_addrword_t data, HAL_SavedRegisters* regs)
{
    cyg_uint32  clock_stat;
    HAL_READ_UINT32(AT91RM9200_TC1 + _TC_SR, clock_stat);
    __profile_hit(regs->pc);
    return CYG_ISR_HANDLED;
}

int
hal_enable_profile_timer(int resolution)
{
    // The requested resolution is in microseconds. Rather than try to
    // figure out the platform's frequency we use the existing
    // calculations for the system clock. We have
    // CYGNUM_HAL_RTC_PERIOD for a value corresponding to
    // (CYGNUM_HAL_RTC_NUMERATOR / CYGNUM_HAL_RTC_DENOMINATOR)
    // nanoseconds. This gives:
    cyg_uint32 profile_period = (cyg_uint32)(((long long)resolution * (long long)CYGNUM_HAL_RTC_PERIOD * (long long)CYGNUM_HAL_RTC_DENOMINATOR * 1000ll) \
        / (long long) CYGNUM_HAL_RTC_NUMERATOR);
    if (profile_period > 65535) {
        profile_period = 65535;
    }
    resolution = (int)(((long long)profile_period * (long long)CYGNUM_HAL_RTC_NUMERATOR) /
        ((long long)CYGNUM_HAL_RTC_PERIOD * (long long)CYGNUM_HAL_RTC_DENOMINATOR * 1000ll));
      
    cyg_drv_interrupt_create( CYGNUM_HAL_INTERRUPT_TC1, 1, 0, (cyg_ISR_t*) &profile_isr, &profile_dsr, &profile_handle, &profile_interrupt);
    cyg_drv_interrupt_attach(profile_handle);
    
    HAL_WRITE_UINT32(AT91RM9200_TC1 + _TC_CMR, _TC_CMR_WAVESEL_UP_AUTO | _TC_CMR_WAVE | 3);
    HAL_WRITE_UINT32(AT91RM9200_TC1 + _TC_RC,  profile_period);
    HAL_WRITE_UINT32(AT91RM9200_TC1 + _TC_CCR, _TC_CCR_CLKEN | _TC_CCR_SWTRG);
    HAL_WRITE_UINT32(AT91RM9200_TC1 + _TC_IDR, ~_TC_SR_CPCS);
    HAL_WRITE_UINT32(AT91RM9200_TC1 + _TC_IER, _TC_SR_CPCS);
    cyg_drv_interrupt_unmask(CYGNUM_HAL_INTERRUPT_TC1);
    return resolution;
}
#endif

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

// This routine is called to respond to a hardware interrupt (IRQ).  It
// should interrogate the hardware and return the IRQ vector number.
int hal_IRQ_handler(void)
{
    cyg_uint32 vec;

#if CYGINT_HAL_AT91RM9200_AIC_VSR
    // Get the interrupt number.
    HAL_READ_UINT32( _AIC_ISR, vec );
#else
    // If not using the AIC directly, the vector number will have been stored
    // as the SVR for that vector, so can be retrieved with the IVR.
    // We don't just read the ISR because that doesn't work unless the IVR
    // has been read first, which would mean a dummy read.
    HAL_READ_UINT32( _AIC_IVR, vec );
#endif

    while( vec == CYGNUM_HAL_INTERRUPT_SYSTEM )
    {
        cyg_uint32 status, mask;

        // Test DEBUG unit interrupts
        HAL_READ_UINT32( AT91RM9200_DEBUG+AT91_US_CSR, status );
        HAL_READ_UINT32( AT91RM9200_DEBUG+AT91_US_IMR, mask );
        if( status & mask )
        {
            vec = CYGNUM_HAL_INTERRUPT_DEBUG;
            break;
        }

        // Test System Timer unit interrupts
        HAL_READ_UINT32( _ST_SR, status );
        HAL_READ_UINT32( _ST_IMR, mask );
        if( status & mask )
        {
            status &= mask;
            // Must decode fully as otherwise the information
            // is lost (_ST_SR clears to 0 on read)
            if ( status & _ST_SR_PITS )
                vec = CYGNUM_HAL_INTERRUPT_PIT;
            else if ( status & _ST_SR_WDOVF )
                vec = CYGNUM_HAL_INTERRUPT_WDOVF;
            else if ( status & _ST_SR_RTTINC )
                vec = CYGNUM_HAL_INTERRUPT_RTTINC;
            else if ( status & _ST_SR_ALMS )
                vec = CYGNUM_HAL_INTERRUPT_ALM;
            else // This should not happen
                CYG_FAIL("Unexpected ST status");
            break;
        }

        // Test Power Management Controller interrupts
        HAL_READ_UINT32( _PMC_SR, status );
        HAL_READ_UINT32( _PMC_IMR, mask );
        if( status & mask )
        {
            vec = CYGNUM_HAL_INTERRUPT_PMC;
            break;
        }
        
        // Test Real Time Clock unit interrupts
        HAL_READ_UINT32( _RTC_SR, status );
        HAL_READ_UINT32( _RTC_IMR, mask );
        if( status & mask )
        {
            vec = CYGNUM_HAL_INTERRUPT_RTCH;
            break;
        }

        // If we fall through, we go to the SYSTEM vector.
        break;
    }

    
    // This interrupt controller won't allow another interrupt to occur
    // until the EOI register has been written (it supports up to 8 'stacked'
    // interrupts.  However, this doesn't work well with the eCos interrupt
    // strategy which may not complete an interrupt handling until much
    // later since this is typically done by a DSR (or in the case of the
    // network stacks, even a thread)  By simply resetting this here, we
    // tell the AIC to generate new interrupts as soon as they occur.  The
    // eCos method of masking/unmasking individual interrupts then handles
    // the details.
    HAL_WRITE_UINT32(_AIC_EOICR, 1);
    return vec;
}

//
// Interrupt control
//

void hal_interrupt_mask(int vector)
{
    CYG_ASSERT(vector <= CYGNUM_HAL_ISR_MAX &&
               vector >= CYGNUM_HAL_ISR_MIN , "Invalid vector");
    /* FIXME: Can't mask all system interrupts just because one needs it */
   if( vector >= CYGNUM_HAL_INTERRUPT_DEBUG )
     return;
//       vector = CYGNUM_HAL_INTERRUPT_SYSTEM;
    HAL_WRITE_UINT32(_AIC_IDCR, (1 << vector));
}

void hal_interrupt_unmask(int vector)
{
    CYG_ASSERT(vector <= CYGNUM_HAL_ISR_MAX &&
               vector >= CYGNUM_HAL_ISR_MIN , "Invalid vector");

    if( vector >= CYGNUM_HAL_INTERRUPT_DEBUG )
        vector = CYGNUM_HAL_INTERRUPT_SYSTEM;

    HAL_WRITE_UINT32(_AIC_IECR, (1 << vector));
}

void hal_interrupt_acknowledge(int vector)
{
    CYG_ASSERT(vector <= CYGNUM_HAL_ISR_MAX &&
               vector >= CYGNUM_HAL_ISR_MIN , "Invalid vector");
#if 0 // Not here
    {
        cyg_uint32 val;
        *_tb++ = 0xDEADDEAD;
        HAL_READ_UINT32(_AIC_ISR, val);  *_tb++ = val;
        HAL_READ_UINT32(_AIC_IPR, val);  *_tb++ = val;
        HAL_READ_UINT32(_AIC_IMR, val);  *_tb++ = val;
    }
    HAL_WRITE_UINT32(_AIC_EOICR, (1 << vector));
    {
        cyg_uint32 val;
        *_tb++ = 0xB00BB00B;
        HAL_READ_UINT32(_AIC_ISR, val);  *_tb++ = val;
        HAL_READ_UINT32(_AIC_IPR, val);  *_tb++ = val;
        HAL_READ_UINT32(_AIC_IMR, val);  *_tb++ = val;
    }
#endif
}

void hal_interrupt_configure(int vector, int level, int up)
{
    cyg_uint32 smr;
    CYG_ASSERT(vector <= CYGNUM_HAL_ISR_MAX &&
               vector >= CYGNUM_HAL_ISR_MIN, "Invalid vector");

    if( vector >= CYGNUM_HAL_INTERRUPT_DEBUG )
        vector = CYGNUM_HAL_INTERRUPT_SYSTEM;

    HAL_READ_UINT32(_AIC_SMR + 4*vector, smr );
    if (level)
        smr &= ~0x00000020; // clear edge bit of SRCTYPE
    else // edge triggered
        smr |= 0x00000020; // set edge bit of SRCTYPE
    if (up)
        smr |= 0x00000040; // set high level/rising edge bit of SRCTYPE
    else // edge triggered
        smr &= ~0x00000040; // clear high level/rising edge bit of SRCTYPE
    HAL_WRITE_UINT32(_AIC_SMR + 4*vector, smr );
}

void hal_interrupt_set_level(int vector, int level)
{
    cyg_uint32 smr;
    CYG_ASSERT(vector <= CYGNUM_HAL_ISR_MAX &&
               vector >= CYGNUM_HAL_ISR_MIN, "Invalid vector");
    CYG_ASSERT(level <= 7 &&
               level >= 0, "Invalid interrupt priority level");

    if( vector >= CYGNUM_HAL_INTERRUPT_DEBUG )
        vector = CYGNUM_HAL_INTERRUPT_SYSTEM;
    
    HAL_READ_UINT32(_AIC_SMR + 4*vector, smr );
    smr &= ~0x00000007; // clear PRIOR
    smr |= level & 0x7;
    HAL_WRITE_UINT32(_AIC_SMR + 4*vector, smr );
}

//
// Reset the processor/board
//
void cyg_hal_at91rm9200_reset(void)
{
    cyg_uint32 stat;
    // Reset the watchdog & force it to timeout
    HAL_WRITE_UINT32(_ST_WDMR, (_ST_WDMR_RSTEN|_ST_WDMR_EXTEN|0x1));  // Minimal count
    HAL_WRITE_UINT32(_ST_CR, _ST_CR_WDRST);        // Reset watchdog
    HAL_READ_UINT32(_ST_SR, stat);                 // Clear status
    while (true) {
        HAL_READ_UINT32(_ST_SR, stat);
        if ((stat & _ST_SR_WDOVF) != 0) {
            diag_printf("Why no RESET?\n");
        }
    }
}

//--------------------------------------------------------------------------
// End at91rm9200_misc.c
