/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#include <linux/config.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <linux/kernel_stat.h>
#include <linux/delay.h>
#include <linux/vmalloc.h>
#include <linux/module.h>

#include <asm/mips-boards/ar7.h>
#include <asm/mips-boards/ar7int.h>
#include <asm/mips-boards/ar7_vlynq.h>
#include <asm/mach_avm.h>
#include <asm/mips-boards/ar7_vlynq.h>
#include <asm/mach-ar7/hw_vlynq.h>
#include <asm/mach-ar7/hw_reset.h>
#include <asm/mach-ar7/hw_gpio.h>

#ifdef DEBUGVLYNQ 
#define DEBUG_VLYNQ(args...) printk(KERN_INFO "###VLYNQ###: " args)
#else
#define DEBUG_VLYNQ(args...) /* args */
#endif

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
struct _vlynq_context *vlynq_config[MAX_VLYNQ_DEVICES];
int avm_vlync_double_speed = 0;

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ar7_vlynq_startup_link(volatile struct _vlynq_registers *V, unsigned int vlynq_clock, unsigned int reset_bit) {
    unsigned int count, clk, divisor;

    printk("[ar7_vlynq_startup_link] \n");

    /*----------------------------------------------------------------------------------*\
     * device aus den reset holen
    \*----------------------------------------------------------------------------------*/
    if(reset_bit != (unsigned int)-1)
        ar7_reset_device(reset_bit, 10);
    V->local.Control.Bits.reset        = 1;
    ndelay(100);
    V->local.Control.Register          = 0;

    /*----------------------------------------------------------------------------------*\
     * clock divider einstellen
    \*----------------------------------------------------------------------------------*/
    if(avm_vlync_double_speed) {
        printk("[speedup]: vlynq_clock(%u) -> vlynq_clock(%u)\n", vlynq_clock, vlynq_clock << 1);
        vlynq_clock <<= 1;
    }
    clk = ar7_get_clock(avm_clock_id_system);
    divisor = clk / vlynq_clock;
    if(clk / divisor != vlynq_clock) {
        printk(KERN_WARNING "Warning: configurated vlynq clock not reached (config %uHz) set %uHz\n", vlynq_clock, clk / divisor);
    }
    
    V->local.Control.Bits.clkdir       = 1; /* 1 clk from divisor */

    V->local.Control.Bits.clkdiv       = divisor-1; /* clkdiv + 1 = divisor*/

    /*----------------------------------------------------------------------------------*\
     * pruefen ob der Link vorhanden ist
    \*----------------------------------------------------------------------------------*/
    for(count = 0 ; count < AR7_VLYNQ_INIT_RETRY ; count++) {
        udelay(64); /*--- 64 uSekunden warten ----*/
        if(V->local.Status.Bits.link)
            break;
    }
    if(V->local.Status.Bits.link == 0) {
        V->local.Control.Bits.reset        = 1;
        ar7_put_device_into_reset(reset_bit);
        printk(KERN_ERR "Error: vlynq link could not be established\n");
        return 1;
    }
    return 0;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ar7_vlynq_release_link(unsigned int instance) {

	unsigned int reset_bit;

	if ( instance >= MAX_VLYNQ_DEVICES )
		return 1;
		
	switch ( instance ) {
		case 0:
			reset_bit=VLYNQ0_RESET_BIT;
			break;
		case 1:
			reset_bit=VLYNQ1_RESET_BIT;
			break;
		default:
			return 1;
	}
	/*--- Schicke VlynqController in den Reset ---*/
    ar7_put_device_into_reset(reset_bit);
    
    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ar7_vlynq_init(unsigned int instance) {

    unsigned int reset_bit;
    unsigned int int_nr, ret;
   
    DEBUG_VLYNQ("[ar7_vlynq_init] MAX_VLYNQ_DEVICES %d\n",MAX_VLYNQ_DEVICES);

    printk("[ar7_vlynq_init] device %u\n", instance);
    /* Wurde Speicher für die Instance angelegt? */
    if ( vlynq_config[instance] == NULL ) {
        DEBUG_VLYNQ("[ar7_vlynq_init] vlynq_config[%d] == NULL\n", instance);
        return 1;
    }
    		
    /* Wurde die Vlynqinstanze bereits initialisiert? */
    if( vlynq_config[instance]->vlynq==NULL) {
        switch(instance) {
            case 0:
                vlynq_config[instance]->vlynq = (volatile struct _vlynq_registers *)AR7_VLYNQ0_CTRL_BASE;
                reset_bit = VLYNQ0_RESET_BIT;
                int_nr = AR7INT_VLYNQ0;
                break;
            case 1:
                vlynq_config[instance]->vlynq = (volatile struct _vlynq_registers *)AR7_VLYNQ1_CTRL_BASE;
                reset_bit = VLYNQ1_RESET_BIT;
                int_nr = AR7INT_VLYNQ1;
                break;
            default:
                return 3;
        }

        printk("[ar7_vlynq_init] device %d int_nr %d rest_bit %d CTRL_BASE 0x%x\n", instance, int_nr, reset_bit, vlynq_config[instance]->vlynq);

        if(ar7_vlynq_startup_link(vlynq_config[instance]->vlynq, CONFIG_VLYNQ_SUPPORT_CLK, reset_bit)) {
            return 2;
        }

        /*--- register vlynq interrupt ---*/ 
        ret = request_irq(int_nr, vlynq_interrupt, SA_INTERRUPT, instance?"VLYNQ1":"VLYNQ0", vlynq_config[instance]);
        if (ret) {
            printk(KERN_INFO "[ar7_vlynq_init] request_irq %d failed, reason %d\n", int_nr, ret);
            return ret;
        }
    }

    return 0; /* success */
}

#if 0
/*--- zum testen ---*/
late_initcall(ar7_vlynq_init);
#endif

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ar7_vlynq_config_memory_windows(volatile struct _vlynq_registers_half *VL, 
        volatile struct _vlynq_registers_half *VR, struct ___vlynq_Rx_Address LocalWindows[], 
        unsigned int LocalWindowCount, unsigned int RemoteTxAddr) {

    unsigned int i;
    if(LocalWindowCount > 4) {
        printk(KERN_ERR "ERROR: vlynq: maximal 4 windows allowed\n");
        return 1;
    }

    VR->Tx_Address = RemoteTxAddr;
    DEBUG_VLYNQ("Tx_Address=0x%.8x\n", RemoteTxAddr);

    for(i = 0 ; i < LocalWindowCount ; i++) {
        VL->Rx_Address[i] = LocalWindows[i];
        DEBUG_VLYNQ("Rx_Address=0x%.8x\n", LocalWindows[i].Offset);
        DEBUG_VLYNQ("Rx_Size=0x%.8x\n\n", LocalWindows[i].Size);
    }
    for( ; i < 4 ; i++) {
        VL->Rx_Address[i].Size   = 0;
        VL->Rx_Address[i].Offset = 0;
    }

    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ar7_vlynq_config_interrupt(unsigned int instance, volatile struct _vlynq_registers_half *V, volatile struct _vlynq_interrupts *Irq) {
    unsigned int val;

    if(Irq->LocalEnable == VLYNQ_INT_OFF) {
        return 0;
    }
    /* ==20070805 SK==
     * Vlynq status interrupts are registered and enabled although any handling is missing.
     * We need to register another irq routine (irq number = Vlynq virtual irq plus status
     * interrupt vector --> Irq->irq) to process status interrupts or we can disable status
     * interrupts.
     * !To distinguish between status interrupts and (remote) device interrupts different
     * vectors have to be assigned. Mind the note concerning interrupt priority status
     * register as well.
     */
    /* Map local module status interrupts to interrupt vector*/
    val = ((Irq->vector) & 0x1F) << VLYNQ_CTL_INTVEC_SHIFT ;

    /*--- set vector to virtual interrupt ---*/
    Irq->irq = vlynq_vector_to_irq(instance, Irq->vector);
  
    /* enable local module status interrupts */
    val |= 1 << VLYNQ_CTL_INTEN_SHIFT;          /*--- enable später ---*/
  
    if (Irq->LocalEnable == VLYNQ_INT_LOCAL ) {
        /*set the intLocal bit*/
        val |= 1 << VLYNQ_CTL_INTLOCAL_SHIFT;
    }
  
    /* Irrespective of whether interrupts are handled locally, program
     * int2Cfg. Error checking for accidental loop(when intLocal=0 and int2Cfg=1
     * i.e remote packets are set intPending register->which will result in 
     * same packet being sent out) has been done already
     */
    if (Irq->RemoteEnable == VLYNQ_INT_ROOT_ISR) {
        /* Set the int2Cfg register, so that remote interrupt
         * packets are written to intPending register */
        val |= 1 << VLYNQ_CTL_INT2CFG_SHIFT;

        /* Set intPtr register to point to intPending register */
        V->Interrupt_Pointer = ((unsigned int)&(V->Interrupt_Pending_Set) & 0xFF);
    } else {
        /*set the interrupt pointer register*/
        /*--- VL->Interrupt_Pointer = local interrupt pointer adresse ---*/
        /*--- V->Interrupt_Pointer = TODO ---*/
        panic("set interrupt pointer register to Interruptcontroller not implemented\n");
        val &= ~(1 << VLYNQ_CTL_INT2CFG_SHIFT);
    }
    DEBUG_VLYNQ("ControlReg (0x%08x) = 0x%08x\n",&V->Control, V->Control.Register);
    DEBUG_VLYNQ("Set Interr.-Ctrl = 0x%08x\n",val);
    V->Control.Register = (V->Control.Register & ~((1 << VLYNQ_CTL_INT2CFG_SHIFT) || 
                                                   (1 << VLYNQ_CTL_INTLOCAL_SHIFT) || 
                                                   (1 << VLYNQ_CTL_INTEN_SHIFT))) | val;
                                                   
    DEBUG_VLYNQ("ControlReg (0x%08x) = 0x%08x\n",&V->Control, V->Control.Register);
    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ar7_vlynq_init_link(unsigned int instance) {
    unsigned int ret;
    volatile struct _vlynq_registers *V = NULL;
    struct _vlynq_context* context=NULL;

	if( instance >= MAX_VLYNQ_DEVICES )
		return 1;
  
  	context=vlynq_config[instance];
  	
  	if(context==NULL)
  		return 2;
  		
    V = context->vlynq;
          
    DEBUG_VLYNQ("Remote => Local\n");
    ret = ar7_vlynq_config_memory_windows(&(V->local), &(V->remote), 
                                             context->Remote2Local.Windows, 
                                             context->Remote2Local.WindowCount, 
                                             context->Remote2Local.Tx_Address);
    if(ret)
        return ret;
        
    DEBUG_VLYNQ("Local => Remote\n");
    ret = ar7_vlynq_config_memory_windows(&(V->remote), &(V->local), 
                                             context->Local2Remote.Windows, 
                                             context->Local2Remote.WindowCount, 
                                             context->Local2Remote.Tx_Address);
    if(ret)
        return ret;

    /*--------------------------------------------------------------------------------------*\
     * ggf. noch diverse Parameter setzen
    \*--------------------------------------------------------------------------------------*/

	/*--------------------------------------------------------------------------------------*\
     * Configure Interrupt Priority Vector Status/Clear Register
    \*--------------------------------------------------------------------------------------*/
	#warning "Sascha: Sollte in eine eigene Funktion ausgelagert\n"
	V->local.Interrupt_Priority.Bits.intstat=0x7;
	V->local.Interrupt_Priority.Bits.nointpend=0;
    /*--------------------------------------------------------------------------------------*\
     * Interrupt aktivieren
    \*--------------------------------------------------------------------------------------*/
    DEBUG_VLYNQ("LOCAL INTERRUPT aktivieren\n");
    ret = ar7_vlynq_config_interrupt(instance, &(V->local), &(context->LocalIrq));
    if(ret) {
        return ret;
    }
    DEBUG_VLYNQ("REMOTE INTERRUPT aktivieren\n");
    ret = ar7_vlynq_config_interrupt(instance, &(V->remote), &(context->RemoteIrq));
    if(ret) {
        return ret;
    }

    /*--------------------------------------------------------------------------------------*\
    \*--------------------------------------------------------------------------------------*/
    DEBUG_VLYNQ("Startup Vlynq Link\n");

    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
struct _vlynq_context *ar7_vlynq_alloc_context(unsigned int instance) {
    struct _vlynq_context *C;

    if(instance >= MAX_VLYNQ_DEVICES)
        return 0;

    C = kmalloc(sizeof(struct _vlynq_context), GFP_ATOMIC);
    if(C) {
        memset(C, 0, sizeof(*C));
        C->instance = instance;
        vlynq_config[instance]=C;
    }
    return C;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ar7_vlynq_free_context(unsigned int instance) {
	
	int int_nr = 0; /* Interrupt fuer*/
	
	if (instance >= MAX_VLYNQ_DEVICES)
		return 1;
		
	switch(instance) {
        case 0:
            int_nr = AR7INT_VLYNQ0;
            break;
        case 1:
            int_nr = AR7INT_VLYNQ1;
            break;
        default:
            return 1;
    }
    
    free_irq(int_nr, vlynq_config[instance]);
		
	ar7_vlynq_release_link( instance );
	
    kfree(vlynq_config[instance]);
    vlynq_config[instance]=NULL;
    
    return 0;
}



/*------------------------------------------------------------------------------------------*\
        V->local.Control.Bits.reset        = 0;
        V->local.Control.Bits.iloop        = 0;
        V->local.Control.Bits.aopt_disable = 0;
        V->local.Control.Bits.int2cfg      = 1: 
        V->local.Control.Bits.intvec       = 0; / * 5-Bit Vector * /
        V->local.Control.Bits.intenable    = 0; / * 1 enable, 0 disable * /
        V->local.Control.Bits.intlocal     = 1; / * 1 local, 0 remote * /
        V->local.Control.Bits.clkdir       = 1; / * 1 clk from divisor * /
        V->local.Control.Bits.clkdiv       = divisor;
        V->local.Control.Bits.txfastpath   = 0; / * buffered or not * /
        V->local.Control.Bits.rtmenable    = 0;
        V->local.Control.Bits.rtmvalidwr   = 0;
        V->local.Control.Bits.rxsampleval  = 0;
        V->local.Control.Bits.sclkpudis    = 
        V->local.Control.Bits.pmen         = 
\*------------------------------------------------------------------------------------------*/

/* =============== EXPORT SYMBOLS ============= */
EXPORT_SYMBOL(vlynq_get_irq);
EXPORT_SYMBOL(vlynq_irq_vector_setup);
EXPORT_SYMBOL(ar7_vlynq_init);
EXPORT_SYMBOL(ar7_vlynq_init_link);
EXPORT_SYMBOL(ar7_vlynq_free_context);
EXPORT_SYMBOL(ar7_vlynq_alloc_context);
EXPORT_SYMBOL(vlynq_vector_to_irq);
EXPORT_SYMBOL(vlynq_interrupt);
EXPORT_SYMBOL(vlynq_irq_status);
EXPORT_SYMBOL(vlynq_irq_enable_mask);
EXPORT_SYMBOL(vlynq_irq_enable);
EXPORT_SYMBOL(vlynq_irq_disable);
EXPORT_SYMBOL(vlynq_irq_ack);


