/*=================================================================
//
//        slebintr.c
//
//        SPARClite HAL interrupt manipulation test
//
//==========================================================================
//####COPYRIGHTBEGIN####
//
// -------------------------------------------
// The contents of this file are subject to the Cygnus eCos Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://sourceware.cygnus.com/ecos
// 
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
// License for the specific language governing rights and limitations under
// the License.
// 
// The Original Code is eCos - Embedded Cygnus Operating System, released
// September 30, 1998.
// 
// The Initial Developer of the Original Code is Cygnus.  Portions created
// by Cygnus are Copyright (C) 1998,1999 Cygnus Solutions.  All Rights Reserved.
// -------------------------------------------
//
//####COPYRIGHTEND####
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):     dsm
// Contributors:    dsm, nickg
// Date:          1998-06-18
//####DESCRIPTIONEND####
*/

#include <pkgconf/system.h>

#include <pkgconf/hal.h>

#include <cyg/infra/cyg_type.h>

#include <cyg/infra/cyg_ass.h>

#include <cyg/infra/testcase.h>

#include <cyg/hal/hal_arch.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/hal/hal_diag.h>
#include <cyg/hal/hal_clock.h>

#include <pkgconf/infra.h>

#include <cyg/infra/diag.h>

#define DELAY(x) \
    CYG_MACRO_START int i; for (i = 0; i < x; i++); CYG_MACRO_END

#define DLA 100

static int l, u, m, j;

int levels[ 16 ];
int    ups[ 16 ];
int  masks[ 16 ];
int   reqs[ 16 ];

#define XDIGIT( q ) (q + ((q < 10) ? '0' : ('A'-10) ))

#define SETSTR( x, y, str ) CYG_MACRO_START \
        str[0] = XDIGIT( x ); \
        str[1] = XDIGIT( y ); \
CYG_MACRO_END

static char lstr[] = "xy Bad level";
static char mstr[] = "xy Bad  mask";
static char ustr[] = "xy Bad    up";


static void checkallbut( int z )
{
    int i;
    for ( i = 1; i < 16; i++ ) {
        int level, up, hipri, mask, req;
        if ( z == i )
            continue;

        SETSTR( i, z, lstr );
        SETSTR( i, z, mstr );
        SETSTR( i, z, ustr );

        HAL_INTERRUPT_QUERY_INFO( i, level, up, hipri, mask, req);
        l = level;
        u = up;
        m = mask;
        j = i;

#if 0 // for manual testing really...
        if ( level != levels[i] )
            CYG_TEST_INFO( lstr );
        if (    up !=    ups[i] )
            CYG_TEST_INFO( ustr );
        if (  mask !=  masks[i] )
            CYG_TEST_INFO( mstr );
        if ( (level != levels[i] ) 
        |    (   up !=    ups[i] ) 
        |    ( mask !=  masks[i] ) ) {
            CYG_TEST_INFO( "Re-reading" );
            HAL_INTERRUPT_QUERY_INFO( i, level, up, hipri, mask, req);
        }
#endif
        CYG_TEST_CHECK( level == levels[i], lstr );
        CYG_TEST_CHECK(    up ==    ups[i], ustr );
        CYG_TEST_CHECK(  mask ==  masks[i], mstr );
    }
}

// input is the active phase of the chosen interrupt.  It is assumed that
// the source is normally level-sensititve rather than edge-sensitive.
static void interferewith( int which, int input )
{
    int level, up, hipri, mask, req;

    // Interfere with interrupt 'which'
    HAL_INTERRUPT_CONFIGURE( which, 1, input ); // should be no change

    checkallbut( 0 ); // so don't exclude any of them

    HAL_INTERRUPT_CONFIGURE( which, 1, !input ); // make it other-sensitive
    DELAY( DLA );
    HAL_INTERRUPT_ACKNOWLEDGE( which );
    DELAY( DLA );
    HAL_INTERRUPT_QUERY_INFO( which, level, up, hipri, mask, req);
    CYG_TEST_CHECK( 0 != level , "Int not level-sensitive (-ve level)" );
    if ( input )
        CYG_TEST_CHECK( 0 == up, "Int high level (-ve level)" );
    else
        CYG_TEST_CHECK( 0 != up, "Int low level (-ve level)" );
    CYG_TEST_CHECK( 0 !=  mask , "Int unmasked (-ve level)" );
    CYG_TEST_CHECK( 0 !=   req , "Int not requesting (-ve level)" );
    checkallbut( which ); // don't check #which, we're messing with it
    
    HAL_INTERRUPT_CONFIGURE( which, 0, input ); // edge, default sense
    DELAY( DLA );
    HAL_INTERRUPT_ACKNOWLEDGE( which );
    DELAY( DLA );
    HAL_INTERRUPT_QUERY_INFO( which, level, up, hipri, mask, req);
    CYG_TEST_CHECK( 0 == level , "Int not edge-sensitive (+ve edge)" );
    if ( input )
        CYG_TEST_CHECK( 0 != up, "Int low edge (+ve edge)" );
    else
        CYG_TEST_CHECK( 0 == up, "Int high edge (+ve edge)" );
    CYG_TEST_CHECK( 0 !=  mask , "Int unmasked (+ve edge)" );
    CYG_TEST_CHECK( 0 ==   req , "Int requesting (+ve edge)" );
    checkallbut( which ); // don't check #which, we're messing with it
    
    HAL_INTERRUPT_CONFIGURE( which, 0, !input ); // edge, opposite sense
    DELAY( DLA );
    HAL_INTERRUPT_ACKNOWLEDGE( which );
    DELAY( DLA );
    HAL_INTERRUPT_QUERY_INFO( which, level, up, hipri, mask, req);
    CYG_TEST_CHECK( 0 == level , "Int not edge-sensitive (-ve edge)" );
    if ( input )
        CYG_TEST_CHECK( 0 == up, "Int high edge (-ve edge)" );
    else
        CYG_TEST_CHECK( 0 != up, "Int low edge (-ve edge)" );
    CYG_TEST_CHECK( 0 !=  mask , "Int unmasked (-ve edge)" );
    CYG_TEST_CHECK( 0 ==   req , "Int requesting (-ve edge)" );
    checkallbut( which ); // don't check #which, we're messing with it
    
    HAL_INTERRUPT_CONFIGURE( which, 1, input ); // back to original value
    DELAY( DLA );
    HAL_INTERRUPT_ACKNOWLEDGE( which );
    DELAY( DLA );
    checkallbut( 0 ); // so don't exclude any of them
}

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

#ifndef CYGPKG_KERNEL
// then the clock is not initialized!
#undef HAL_CLOCK_READ
// so provide a dumb counter, so we do the test a number of times anyway.
static int pseudotime = 0;
#define HAL_CLOCK_READ( _pval_ ) *(_pval_) = \
   ((pseudotime > 10) ? (pseudotime = 0) : ++pseudotime)
#endif

static int start( void )
{
    // We'll mess about with these interrupt sources:
    // 13   : EX_IRQ13 from expansion board, active HIGH
    // 12   : EX_IRQ12 from expansion board, active LOW
    //  4   : EX_IRQ4 from expansion board, active LOW
    //  3   : EX_IRQ3 from expansion board, active HIGH

    int i, hipri;
    for ( i = 1; i < 16; i++ ) {
        HAL_INTERRUPT_QUERY_INFO( i, levels[i], ups[i],
                                  hipri, masks[i], reqs[i]);
    }
    CYG_TEST_CHECK( 0 !=  masks[13], "Int 13 unmasked initially" );
    CYG_TEST_CHECK( 0 !=  masks[12], "Int 12 unmasked initially" );
    CYG_TEST_CHECK( 0 !=  masks[ 4], "Int  4 unmasked initially" );
    CYG_TEST_CHECK( 0 !=  masks[ 3], "Int  3 unmasked initially" );
    CYG_TEST_CHECK( 0 ==   reqs[13], "Int 13 requests initially" );
    CYG_TEST_CHECK( 0 ==   reqs[12], "Int 12 requests initially" );
    CYG_TEST_CHECK( 0 ==   reqs[ 4], "Int  4 requests initially" );
    CYG_TEST_CHECK( 0 ==   reqs[ 3], "Int  3 requests initially" );
    CYG_TEST_CHECK( 0 != levels[13], "Int 13 edgetrig initially" );
    CYG_TEST_CHECK( 0 != levels[12], "Int 12 edgetrig initially" );
    CYG_TEST_CHECK( 0 != levels[ 4], "Int  4 edgetrig initially" );
    CYG_TEST_CHECK( 0 != levels[ 3], "Int  3 edgetrig initially" );
    CYG_TEST_CHECK( 0 !=    ups[13], "Int 13 not up initially" );
    CYG_TEST_CHECK( 0 ==    ups[12], "Int 12 is up initially" );
    CYG_TEST_CHECK( 0 ==    ups[ 4], "Int  4 is up initially" );
    CYG_TEST_CHECK( 0 !=    ups[ 3], "Int  3 not up initially" );

    checkallbut( 0 ); // don't exclude any of them

    // I want to run this loop for 100 centiSeconds, so that 100 clock
    // interrupts have occurred whilst interfering with the other interrupt
    // state, to provoke possible problems with interactions there.  On a
    // 100MHz 86832 with default configuration, this usually gives 1200
    // loops in total, 12 per centiSecond.  But with config variance and
    // caching behaviour, it's quite possible for this loop to take much
    // longer, if it takes over 1/2 a centiSecond, aliasing effects could
    // cause the timing to fail completely, causing test timeouts.  Hence
    // the additional loop limit of 20 times round the inner loop, aiming
    // for a maximum elapsed time of 20 S maximum, plus extra chances to
    // break out part way through the loop if a tick has passed.

    hipri = 0;
    CYG_TEST_INFO( "About to configure interrupts" );
    for ( i = 0; i < 100; i++ ) {
        int t1, t2, j;
        HAL_CLOCK_READ( &t1 );
        // Do this while/until there is a clock tick
        // ie. some interrupt activity:
        for ( j = 0; j < 20; j++ ) {
            t2 = t1;
            hipri++;
            interferewith( 13, 1 );
            interferewith( 3, 1 );
            interferewith( 4, 0 );
            HAL_CLOCK_READ( &t1 );
            if ( t1 < t2 )
                break;                  // clock has wrapped
            t2 = t1;
            interferewith( 12, 0 );
            interferewith( 3, 1 );
            interferewith( 3, 1 );
            HAL_CLOCK_READ( &t1 );
            if ( t1 < t2 )
                break;                  // clock has wrapped
            t2 = t1;
            interferewith( 4, 0 );
            interferewith( 13, 1 );
            interferewith( 12, 0 );
            interferewith( 12, 0 );
            HAL_CLOCK_READ( &t1 );
            if ( t1 < t2 )
                break;                  // clock has wrapped
        }
    }
    CYG_TEST_PASS( "Configured interrupts 3,4,12 & 13 a great deal" );
    return hipri;
}

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

externC void
#ifdef CYGPKG_KERNEL
cyg_user_start( void )
#else
cyg_start( void )
#endif
{
    int loops;
    CYG_TEST_INIT();
    CYG_TEST_INFO( "cyg_user_start()" );
    HAL_ENABLE_INTERRUPTS();
    loops = start();
    // Typically about 1200 loops execute on the 33MHz 86832;
    // I hoped to put in a check that we hadn't wasted time in
    // spurious interrupts, but kernel instrumentation, for example,
    // is more than enough to slow the world down... so keep this
    // very weak test in, as a placeholder.
    CYG_TEST_CHECK( 100 <= loops, "Not enough tests executed" );
    CYG_TEST_EXIT( "All done" );
}

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

/* EOF slebintr.c */
