/******************************************************************************
 * FILE PURPOSE:    ESWITCH Driver support Source Code
 ******************************************************************************
 * FILE NAME:       eswitcHalLx.c
 *
 * DESCRIPTION:     ESWITCH HAL OS port source code
 * 
 * REVISION HISTORY:
 *   19th NOV. 03 - PSP TII  
 *    8th DEC. 03 - PSP TII  

 *
 * (C) Copyright 2003, Texas Instruments, Inc
 *******************************************************************************/
#include <linux/kernel.h> 
#include <linux/module.h> 
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/delay.h>     
#include <linux/spinlock.h>
#include <linux/proc_fs.h>
#include <asm/io.h>
#include <linux/string.h>

#include "eswitchHalLx.h"
#include "eswitch.h"
#include "eswitchConfig.h"

/* debug */                                 
#define ESWITCH_END_DEBUG 0
#define dbgPrint if (ESWITCH_END_DEBUG) printk

char EswitchSignature[] = "eswitch driver";
static unsigned long irq_flags = 0;

/*---------------------------------------------------------------------------
 * Memory related OS abstraction.
 *--------------------------------------------------------------------------*/
static void os_free(void *mem_ptr) 
{
    dbgPrint("%s : %d.\n", __FUNCTION__, __LINE__);
    kfree(mem_ptr);
}

static int si = 0;
static void  os_free_buffer(OS_RECEIVEINFO *osReceiveInfo, void *mem_ptr) 
{
    dbgPrint("%s : %d.\n", __FUNCTION__, ++si);
    dev_kfree_skb_any(osReceiveInfo);
}

static void  os_free_dev(void *mem_ptr) 
{
    dbgPrint("%s : %d.\n", __FUNCTION__, __LINE__);
    kfree(mem_ptr);
}

static void os_free_dma_xfer(void *mem_ptr) 
{
    dbgPrint("%s : %d.\n", __FUNCTION__, __LINE__);
    kfree(mem_ptr);
}

static void *os_malloc(unsigned int size)
{
    dbgPrint("%s : %d.\n", __FUNCTION__, __LINE__);
    return(kmalloc(size, GFP_KERNEL));
}

static void *os_malloc_dma_xfer(unsigned int size, 
                                void         *mem_base, 
                                unsigned int mem_range)
{
    dbgPrint("%s : %d.\n", __FUNCTION__, __LINE__);
    return(kmalloc(size, GFP_KERNEL));
}

static void *os_malloc_dev(unsigned int size)
{
    dbgPrint("%s : %d.\n", __FUNCTION__, __LINE__);
    return(kmalloc(size, GFP_KERNEL));
}
/*----------------------------------------------------------------------------
 * CRITICAL SECTION ENABLING/DISABLING.
 *--------------------------------------------------------------------------*/
static void os_critical_on(void)
{
    save_and_cli(irq_flags);
}

static void os_critical_off(void)
{
    restore_flags(irq_flags);
}

/*----------------------------------------------------------------------------
 * Cache related abstraction
 *--------------------------------------------------------------------------*/
static void os_cache_invalidate(void *mem_ptr, int size)
{
    dma_cache_inv((unsigned long)mem_ptr, size);
}

static void os_cache_writeback(void *mem_ptr, int size)
{
    dma_cache_wback_inv((unsigned long)mem_ptr, size);
}

static void  hal_drv_unregister_isr(OS_DEVICE *p_dev, int intr)
{
    ESWITCH_PRIVATE_INFO_T *p_eswitch_priv = p_dev->priv;
    intr                                   = LNXINTNUM( AVALANCHE_SWITCH_HOST_DMA_INT );
    free_irq(intr, p_eswitch_priv->eswitch_isr);

    os_free(p_eswitch_priv->eswitch_isr);
}

void eswitch_hal_isr(int irq, void *p_param, struct pt_regs *regs)
{
    ESWITCH_ISR_INFO_T      *p_cb_param       = (ESWITCH_ISR_INFO_T*) p_param;
    ESWITCH_DRV_HAL_INFO_T  *p_drv_hal        = p_cb_param->owner;
    ESWITCH_PRIVATE_INFO_T  *p_eswitch_priv   = p_drv_hal->owner;
    int                      pkts_to_handle   = 0;

    if(!p_eswitch_priv->set_to_close)
        tasklet_schedule(&((ESWITCH_ISR_INFO_T*) p_param)->tasklet);
    else
        ; // back off from doing anything more. We are closing down.
    
}

void eswitch_handle_tasklet(unsigned long data)
{
    ESWITCH_ISR_INFO_T     *p_cb_param     = (ESWITCH_ISR_INFO_T*) data;
    ESWITCH_DRV_HAL_INFO_T *p_drv_hal      = p_cb_param->owner;
    ESWITCH_PRIVATE_INFO_T *p_eswitch_priv = p_drv_hal->owner;
    int                     pkts_to_handle;

    p_cb_param->hal_isr(p_drv_hal->hal_dev, &pkts_to_handle);    
     
    if(!pkts_to_handle)
	 p_drv_hal->hal_funcs->PacketProcessEnd(p_drv_hal->hal_dev); /*TODO*/

    if(!test_bit(0, &p_eswitch_priv->set_to_close))
        tasklet_schedule(&p_cb_param->tasklet);
    else
        ; // Back off from processing packets we are closing down.
}

static void hal_drv_register_isr(OS_DEVICE *p_dev, 
                                 ESWITCH_HAL_ISR_FUNC_T hal_isr, int intr)
{
    ESWITCH_PRIVATE_INFO_T *p_eswitch_priv   = p_dev->priv;
    ESWITCH_DRV_HAL_INFO_T *p_drv_hal        = p_eswitch_priv->drv_hal;
    ESWITCH_ISR_INFO_T     *p_isr_cb_param   = os_malloc(sizeof(ESWITCH_ISR_INFO_T));
    intr                                     = LNXINTNUM(intr);
        
    dbgPrint("osRegister called for the intr %d for device %x and p_isr_cb_param %x.\n", 
             intr, (bit32u)p_dev, (bit32u)p_isr_cb_param);

    p_isr_cb_param->owner       = p_drv_hal;
    p_isr_cb_param->hal_isr     = hal_isr;
    p_eswitch_priv->eswitch_isr = p_isr_cb_param;

    if(request_irq(intr, eswitch_hal_isr, SA_INTERRUPT,
			 EswitchSignature, p_isr_cb_param))
    {
        os_free(p_isr_cb_param);
    }
    else
    {
        tasklet_init(&p_isr_cb_param->tasklet, eswitch_handle_tasklet, (unsigned long)p_isr_cb_param);
    } 
}

static int load_os_funcs(OS_FUNCTIONS *os_func)
{
    dbgPrint("os_init_module: Start\n"); 
    if( os_func == 0 )
    {
        return(sizeof(OS_FUNCTIONS));   
    }
    os_func->Control                    = eswitch_hal_control;
    os_func->CriticalOn                 = os_critical_on;
    os_func->CriticalOff                = os_critical_off;
    os_func->DataCacheHitInvalidate     = os_cache_invalidate;
    os_func->DataCacheHitWriteback      = os_cache_writeback;    
    os_func->DeviceFindInfo             = 0;
    os_func->DeviceFindParmUint         = 0;
    os_func->DeviceFindParmValue        = 0;
    os_func->Free               = os_free;
    os_func->FreeRxBuffer       = os_free_buffer;
    os_func->FreeDev            = os_free_dev;
    os_func->FreeDmaXfer        = os_free_dma_xfer;
    os_func->IsrRegister        = hal_drv_register_isr;
    os_func->IsrUnRegister      = hal_drv_unregister_isr;
    os_func->Malloc             = os_malloc;
    os_func->MallocDev          = os_malloc_dev;
    os_func->MallocDmaXfer      = os_malloc_dma_xfer;
    os_func->MallocRxBuffer     = eswitch_hal_malloc_buffer;
    os_func->Memset             = memset;
    os_func->Printf             = printk;
    os_func->Receive            = 0;
    os_func->SendComplete       = 0;
    os_func->Strcmpi            = eswitch_ci_strcmp;
    os_func->TeardownComplete   = 0;
    os_func->Strstr             = strstr;
    os_func->Strtoul            = simple_strtol;
    os_func->Sprintf            = sprintf;
    os_func->Strlen             = strlen;
    dbgPrint("os_init_module: Leave\n");             
    return(0);
}

int eswitch_drv_cleanup(ESWITCH_DRV_HAL_INFO_T *p_drv_hal)
{
    HAL_DEVICE       *p_hal_dev    = p_drv_hal->hal_dev;
    HAL_FUNCTIONS    *p_hal_funcs  = p_drv_hal->hal_funcs;

    int              ret_val       = p_hal_funcs->Shutdown(p_hal_dev);

    if(ret_val == 0)
        kfree(p_hal_funcs);
    else
        ret_val = -1;
    return (ret_val); 
}

int eswitch_drv_tx_setup(HAL_FUNCTIONS          *p_hal_funcs,
                         HAL_DEVICE             *p_hal_dev,
                         ESWITCH_TX_CHAN_INFO_T *p_tx_chan_info)
{
    int ret_val = 0;
    int count   = 0;
    CHANNEL_INFO chan_info;
    
    dbgPrint("entering TX channel set up\n");             

    chan_info.Channel        = 0;
    chan_info.Direction      = DIRECTION_TX;       
    if((ret_val = p_hal_funcs->ChannelSetup(p_hal_dev,&chan_info,NULL)) != 0)
    {
    	dbgPrint("Error in opening channel %d for TX.\n", count);
        	ret_val = -1;
    }
    else
    {
    	p_tx_chan_info->opened_chan++;
    }    
    dbgPrint("leaving TX channel set up\n");             
    return(ret_val);
}

int eswitch_drv_rx_setup(HAL_FUNCTIONS           *p_hal_funcs,
                         HAL_DEVICE              *p_hal_dev,
                         ESWITCH_RX_CHAN_INFO_T  *p_rx_chan_info)
{
    int ret_val = 0;
    CHANNEL_INFO chan_info;

    chan_info.Channel       = 1;
    chan_info.Direction     = DIRECTION_RX;
    chan_info.Receive       = eswitch_hal_receive;

    dbgPrint("entering RX channel set up\n");             
    if(p_hal_funcs->ChannelSetup(p_hal_dev, &chan_info, p_rx_chan_info) != 0) 
    {
        dbgPrint("Error in opening channel %d for RX.\n", 0);
        ret_val = -1;
    }
    dbgPrint("leaving RX channel set up\n");             
    return(ret_val);
}

int eswitch_drv_start(ESWITCH_PRIVATE_INFO_T *p_eswitch_priv,
                      ESWITCH_TX_CHAN_INFO_T *p_tx_chan_info,
                      ESWITCH_RX_CHAN_INFO_T *p_rx_chan_info,		      
            	      unsigned int           flags)
{
    HAL_FUNCTIONS   *p_hal_funcs      = p_eswitch_priv->drv_hal->hal_funcs;
    HAL_DEVICE      *p_hal_dev        = p_eswitch_priv->drv_hal->hal_dev;    
    ESWITCH_CONFIG  *p_eswitch_config = p_eswitch_priv->p_config;
    int ret_val     = 0;
    
    /* PUSH mechanism of the driver "to push" hardware information to HAL*/
    ret_val = eswitchConfig(p_hal_dev, p_hal_funcs, p_eswitch_config);    
   
    /*Error in setting up the Channels, quit. */
    dbgPrint("ret_val before open %d size of p_eswitch_priv %d \n", ret_val, sizeof(ESWITCH_PRIVATE_INFO_T));
    dbgPrint("size of struct_timer list %d \n", sizeof(p_eswitch_priv->timer[0]));
    if((ret_val == 0) && (ret_val = p_hal_funcs->Open(p_hal_dev)) != 0)
    {        
	ret_val = -1;
    }
    else if(flags & CHAN_SETUP)
    {
        dbgPrint("ret_val before channel setup open %d\n", ret_val);	    
	    
        if(eswitch_drv_tx_setup(p_hal_funcs, p_hal_dev, p_tx_chan_info)!=0)
        {
            dbgPrint("failed to set up tx channel(s).\n");
            ret_val = -1;
        }
    	else if(eswitch_drv_rx_setup(p_hal_funcs, p_hal_dev, p_rx_chan_info)!=0)
    	{
            dbgPrint("failed to set up rx channel.\n");
            ret_val = -1;
    	}
    	else
    	{
            ret_val = 0;
    	}
    }
    dbgPrint("ret_val %d\n", ret_val);
    return (ret_val);
} /* eswitch_drv_start */

int eswitch_drv_tx_teardown(HAL_FUNCTIONS           *p_hal_funcs,
                            HAL_DEVICE              *p_hal_dev,
                            ESWITCH_TX_CHAN_INFO_T  *p_tx_chan_info,
                            unsigned int            flags)
{
    int ret_val = 0;
    int count   = 0;

    /* Let's tear down the TX Channels. */
    if(p_hal_funcs->ChannelTeardown(p_hal_dev, 0, flags) != 0)
    {
	    dbgPrint("Error in tearing down channel %d for TX.\n", count);
	    ret_val = -1;
    }    
    return(ret_val);
}

int eswitch_drv_rx_teardown(HAL_FUNCTIONS *p_hal_funcs,
                            HAL_DEVICE    *p_hal_dev,
                            unsigned int  flags)
{
    int ret_val = 0;

    if(p_hal_funcs->ChannelTeardown(p_hal_dev, 1, flags) != 0) 
    {
        dbgPrint("Error in tearing down channel %d for RX.\n", 0);
        ret_val = -1;
    }
    return(ret_val);
}

int eswitch_drv_stop(ESWITCH_DRV_HAL_INFO_T    *p_drv_hal,
                     ESWITCH_TX_CHAN_INFO_T    *p_tx_chan_info,
                     ESWITCH_RX_CHAN_INFO_T    *p_rx_chan_info,
                     unsigned int              flags)
{
    HAL_DEVICE       *p_hal_dev    = p_drv_hal->hal_dev;
    HAL_FUNCTIONS    *p_hal_funcs  = p_drv_hal->hal_funcs;
    int ret_val                    = 0;

    if(flags & CHAN_TEARDOWN)
    {
        unsigned int chan_flags = 0;

    if(flags & FREE_BUFFER) chan_flags |= 0x4; /* full tear down */
    if(flags & BLOCKING)    chan_flags |= 0x8; /* blocking call  */

    if(eswitch_drv_tx_teardown(p_hal_funcs, p_hal_dev, p_tx_chan_info, chan_flags | 0x1) != 0)
    {
    	dbgPrint("The tx channel teardown failed.\n");
        ret_val = -1;
    }
    else if(eswitch_drv_rx_teardown(p_hal_funcs, p_hal_dev, chan_flags | 0x2) != 0)
    {
        dbgPrint("The rx channel teardown failed.\n");
        ret_val = -1;
    }
    }
    if(ret_val == 0)
    {
	int close_flags = 1;
	if(flags & FREE_BUFFER)
	{
	    close_flags = 2;
	}
	if(p_hal_funcs->Close(p_hal_dev, close_flags) != 0)
	{
            ret_val = -1;
	}
    }
    return(ret_val);
}

int eswitch_drv_init_module(ESWITCH_DRV_HAL_INFO_T *p_drv_hal, OS_DEVICE *p_os_dev, int inst)
{
    int ret_val = -1;
    int hal_func_size;
    
    dbgPrint("entering the eswitch init module for the inst %d \n", inst);

    if((p_drv_hal->os_funcs = kmalloc(sizeof(OS_FUNCTIONS), GFP_KERNEL)) == NULL)
    {
        dbgPrint("failed to allocate memory for OS_FUNCTIONS.\n");
    }
    else if(load_os_funcs(p_drv_hal->os_funcs) != 0)
    {
    	dbgPrint("Failed to load OS funcs.\n");
    	os_free(p_drv_hal->os_funcs);
    }
    else if(cpswHalEswitchInitModule(&p_drv_hal->hal_dev, p_os_dev,
                                     &p_drv_hal->hal_funcs, p_drv_hal->os_funcs, 
                                     sizeof(*p_drv_hal->os_funcs),
                                     &hal_func_size, inst) != 0)
    {
        dbgPrint("hal eswitch init module failed for inst %d \n", inst);
        os_free(p_drv_hal->os_funcs);
    }
    else
    {
        /* every thing went well.*/
        dbgPrint("device information found for inst %d.\n", inst);
        ret_val = 0;
    }  
    return (ret_val);  
}


