/******************************************************************************
 * FILE PURPOSE:    Vlynq Linux Device Driver Source
 ******************************************************************************
 * FILE NAME:       vlynq_drv.c
 *
 * DESCRIPTION:     Vlynq Linux Device Driver Source
 *
 * REVISION HISTORY:
 *
 * Date           Description                       Author
 *-----------------------------------------------------------------------------
 * 17 July 2003   Initial Creation                  Anant Gole
 * 17 Dec  2003   Updates                           Sharath Kumar
 * 12 Oct  2004   Revamped for PF 1.0 VLYNQ         Suraj Iyer
 *
 * (C) Copyright 2003, Texas Instruments, Inc
 *******************************************************************************/
#include <linux/config.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/miscdevice.h>
#include <linux/smp_lock.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/capability.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/io.h>

#include "pal_vlynq.h"
#include "register_access.h"
#include <asm-arm/cacheflush.h>

extern Uint32 wlan_mem_addr;     	/* WLAN memory base */
extern Uint32 wlan_reg_addr;     	/* WLAN register area base */

/* Exporting the symbols for the VLYNQ. */
EXPORT_SYMBOL(PAL_vlynqAddDevice);
EXPORT_SYMBOL(PAL_vlynqRemoveDevice);
EXPORT_SYMBOL(PAL_vlynqMapRegion);
EXPORT_SYMBOL(PAL_vlynqUnMapRegion);
EXPORT_SYMBOL(PAL_vlynqGetDevBase);
EXPORT_SYMBOL(PAL_vlynqGetLinkStatus);
EXPORT_SYMBOL(PAL_vlynqGetNumRoot);
EXPORT_SYMBOL(PAL_vlynqGetRoot);
EXPORT_SYMBOL(PAL_vlynqGetNext);
EXPORT_SYMBOL(PAL_vlynqIsLast);
EXPORT_SYMBOL(PAL_vlynqGetChainLength);
EXPORT_SYMBOL(PAL_vlynqGetBaseAddr);
EXPORT_SYMBOL(PAL_vlynqGetRootAtBase);
EXPORT_SYMBOL(PAL_vlynqGetRootVlynq);
EXPORT_SYMBOL(PAL_vlynqChainAppend);
EXPORT_SYMBOL(PAL_vlynqChainUnAppend);
EXPORT_SYMBOL(PAL_vlynqMapIrq);
EXPORT_SYMBOL(PAL_vlynqUnMapIrq);
EXPORT_SYMBOL(PAL_vlynqAddIsr);
EXPORT_SYMBOL(PAL_vlynqRemoveIsr);
EXPORT_SYMBOL(PAL_vlynqRootIsr);
EXPORT_SYMBOL(PAL_vlynqGetIrqCount);
EXPORT_SYMBOL(PAL_vlynqGetForIrq);
EXPORT_SYMBOL(PAL_vlynqSetIrqPol);
EXPORT_SYMBOL(PAL_vlynqGetIrqPol);
EXPORT_SYMBOL(PAL_vlynqSetIrqType);
EXPORT_SYMBOL(PAL_vlynqGetIrqType);
EXPORT_SYMBOL(PAL_vlynqDisableIrq);
EXPORT_SYMBOL(PAL_vlynqEnableIrq);
EXPORT_SYMBOL(PAL_vlynqConfigClock);
EXPORT_SYMBOL(PAL_vlynqInit);
EXPORT_SYMBOL(PAL_vlynqCleanUp);
EXPORT_SYMBOL(PAL_vlynqDump);
EXPORT_SYMBOL(PAL_vlynqIoctl);


EXPORT_SYMBOL(PAL_vlynqDevCbRegister);
EXPORT_SYMBOL(PAL_vlynqDevCbUnregister);
EXPORT_SYMBOL(PAL_vlynqDevFind);
EXPORT_SYMBOL(PAL_vlynqDevGetVlynq);
EXPORT_SYMBOL(PAL_vlynqDevFindIrq);
EXPORT_SYMBOL(PAL_vlynqDevGetResetBit);
EXPORT_SYMBOL(PAL_vlynqDevCreate);
EXPORT_SYMBOL(PAL_vlynqDevDestroy);

#ifdef CONFIG_WLAN_VLYNQ_TEST
extern void	InitDemoInterrupt( void );
#endif        

#define    TI_VLYNQ_VERSION                 "0.4"

#define    ERRPRINT   printk

/* Define the max vlynq ports this driver will support. 
   Device name strings are statically added here */
#define MAX_VLYNQ_PORTS 2

#define DAVINCI_VLYNQ_INT_NUM 31 /* The vlynq is conntected to interrupt 31 in the DaVinci interrupt controller */
#define VLYNQ_TNETW1X50_MEM_SIZE   			0x00080000  /* Total size of the TNETW1150 memory   */
#define VLYNQ_TNETW1X50_REG_SIZE   			0x00100000  /* Size of TNETW1150 registers area, MAC+PHY  */

static int
vlynq_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    return 0;
}

static struct file_operations vlynq_fops = {
    owner:      THIS_MODULE,
    ioctl:      vlynq_ioctl,
};

/* Vlynq device object */
static struct miscdevice vlynq_miscdev [MAX_VLYNQ_PORTS] = {
    { MISC_DYNAMIC_MINOR , "vlynq0", &vlynq_fops },
    { MISC_DYNAMIC_MINOR , "vlynq1", &vlynq_fops },
};

static int vlynq_read_all_vlynq(char *buf, char **start, off_t offset, 
                                int count, int *eof, void *data)
{
    return(PAL_vlynqDump(NULL, PAL_VLYNQ_DUMP_ALL_ROOT, buf, count - 80, eof));  
}

static int vlynq_read_registers(char *buf, char **start, off_t offset, 
                                int count, int *eof, void *data)
{
    return(PAL_vlynqDump(data, PAL_VLYNQ_DUMP_ALL_REGS, buf, count - 80, eof));
}

static int vlynq_read_raw(char *buf, char **start, off_t offset, 
                          int count, int *eof, void *data)
{
    return(PAL_vlynqDump(data, PAL_VLYNQ_DUMP_RAW_DATA, buf, count - 80, eof));
}

static int vlynq_read_status_word(char *buf, char **start, off_t offset, 
                                  int count, int *eof, void *data)
{
    return(PAL_vlynqDump(data, PAL_VLYNQ_DUMP_STS_REG, buf, count - 80, eof));
}

static int vlynq_read_control_word(char *buf, char **start, off_t offset, 
                                   int count, int *eof, void *data)
{
    return(PAL_vlynqDump(data, PAL_VLYNQ_DUMP_CNTL_REG, buf, count - 80, eof));
}


static int vlynq_read_tnet_die_id(char *buf, char **start, off_t offset, 
                                int count, int *eof, void *data)
{
	unsigned int die_lo, die_hi, len;

	die_lo = *(unsigned long*)(wlan_reg_addr + 0x88c);
	die_hi = *(unsigned long*)(wlan_reg_addr + 0x890);
	len = sprintf(buf, "%x%x", die_hi, die_lo);
	*eof = 1;
	return len;
}

/* Proc read function */
static int vlynq_read_link_proc(char *buf, char **start, off_t offset, 
                                int count, int *eof, void *data)
{
    int len = 0;
    int root, root_count;

    char *link_msg[] = {" DOWN "," UP "};

    root_count = PAL_vlynqGetNumRoot();    

    for(root = 0; root < root_count; root++)
    {
        int hop  = 0;
        int link;
        PAL_VLYNQ_HND *p_vlynq_hnd;

        len += sprintf(buf + len, "Vlynq Root 0:\n");
        p_vlynq_hnd = PAL_vlynqGetRoot(root);
         
        if(!p_vlynq_hnd)
            continue;
       
        do
        {
          link = PAL_vlynqGetLinkStatus(p_vlynq_hnd);

          len += sprintf(buf + len, "  Vlynq Bridge %d%d: %s\n", root, hop, link_msg[link]);

          p_vlynq_hnd = PAL_vlynqGetNext(p_vlynq_hnd);

        } while(link && !PAL_vlynqIsLast(p_vlynq_hnd));
    }

    return len;
}


/* Proc function to display driver version */                                                                       
static int                                                                                     
vlynq_read_ver_proc(char *buf, char **start, off_t offset, int count, int *eof, void *data)        
{                                                                                              
	int len=0;                                                                                 
                                                                                               
	len += sprintf(buf +len,"\nTI Linux VLYNQ Driver Version %s\n",TI_VLYNQ_VERSION);         
	return len;                                                                                
}                                                                                              


struct proc_dir_entry *vlynq_proc_root;
/* Wrapper for vlynq ISR */
static irqreturn_t lnx_vlynq_root_isr(int irq, void * arg, struct pt_regs *regs)
{
   PAL_vlynqRootIsr(arg);
   return IRQ_HANDLED;   
}

extern void Davinci_Vlynq_Hardware_Init(void);
int vlynq_init_module(void)
{
    int root_count, root;
    int instance_count = 1;

#ifdef CONFIG_WLAN_VLYNQ_TEST
   	printk("Creating reg_accesss proc entry....\n");
	ra_init();
#endif

    /* Call the vlynq initialization */   
 	printk("********************* VLYNQ Initialization ************************\n");
 	printk("Starting the DaVinci EVM board vlynq initialization...\n");
 	
	/* Iniialize the Morpheus board VLYNQ module */
	Davinci_Vlynq_Hardware_Init();    

    wlan_mem_addr = (unsigned int)ioremap(wlan_mem_addr, VLYNQ_TNETW1X50_MEM_SIZE);
    wlan_reg_addr = (unsigned int)ioremap(wlan_reg_addr, VLYNQ_TNETW1X50_REG_SIZE);
    printk("WLAN memory is remapped to address %x \n",wlan_mem_addr);
    printk("WLAN registers are remapped to address %x \n",wlan_reg_addr);    

    /* If num of configured vlynq ports > supported by driver return error */
    if (instance_count > MAX_VLYNQ_PORTS)
    {
        ERRPRINT("ERROR: vlynq_init_module(): Max %d supported while configured for %d.\n", MAX_VLYNQ_PORTS,
                  instance_count);
        return (-1);
    }

    vlynq_proc_root = proc_mkdir("bus/vlynq",NULL);
    if(!vlynq_proc_root)
        return -ENOMEM;

    create_proc_read_entry("vlynq_data", 0, vlynq_proc_root, vlynq_read_all_vlynq, NULL);

	create_proc_read_entry("tnet_die_id", 0, vlynq_proc_root, vlynq_read_tnet_die_id, NULL);

    root_count = PAL_vlynqGetNumRoot( );
    if(!root_count)
        return (-1);

    for(root = 0; root < root_count; root++)
    {
        PAL_VLYNQ_HND *p_root_vlynq, *p_vlynq_hnd = PAL_vlynqGetRoot(root);
        char name[50];
        int hop = 0, irq;
        unsigned int base_addr;
   
        if(!p_vlynq_hnd)
            continue;	

        if(!PAL_vlynqGetLinkStatus(p_vlynq_hnd))
            continue;

        if(PAL_vlynqGetBaseAddr(p_vlynq_hnd, &base_addr))
            continue;
            
        printk("The vlynq controller is located at address %x\n", base_addr);

        if(base_addr)
        {
            irq = DAVINCI_VLYNQ_INT_NUM;
            printk("The vlynq is using interrupt %d ....\n",irq);
        }
        else
            continue;

        p_root_vlynq = p_vlynq_hnd;

        for(; ;p_vlynq_hnd = PAL_vlynqGetNext(p_vlynq_hnd))
        {
            sprintf(name, "vlynq%d%d_regs", root, hop);
            create_proc_read_entry(name, 0, vlynq_proc_root, vlynq_read_registers, p_vlynq_hnd);

            sprintf(name, "vlynq%d%d_raw", root, hop);
            create_proc_read_entry(name, 0, vlynq_proc_root, vlynq_read_raw, p_vlynq_hnd);
        
            sprintf(name, "vlynq%d%d_status", root, hop);
            create_proc_read_entry(name, 0, vlynq_proc_root, vlynq_read_status_word, p_vlynq_hnd);

            sprintf(name, "vlynq%d%d_control", root, hop);
            create_proc_read_entry(name, 0, vlynq_proc_root, vlynq_read_control_word, p_vlynq_hnd);

            hop++;
   
            if(PAL_vlynqIsLast(p_vlynq_hnd))
                break;
        }

        if(misc_register(&vlynq_miscdev[root]) < -1)
            continue;

        request_irq(irq, lnx_vlynq_root_isr, 0, vlynq_miscdev[root].name, p_root_vlynq);

        printk("Installed misc driver %s: it handles vlynq bridge%d with %d hop(s).\n", 
                vlynq_miscdev[root].name, root, hop);
                
#ifdef CONFIG_WLAN_VLYNQ_TEST
		InitDemoInterrupt();        
#endif
                
    }

    /* Creating proc entry for the devices */
    create_proc_read_entry("avalanche/vlynq_link", 0, NULL, vlynq_read_link_proc, NULL);
    create_proc_read_entry("avalanche/vlynq_ver",  0, NULL, vlynq_read_ver_proc, NULL);

    return 0;
}

void vlynq_cleanup_module(void)
{
    /* 
     * Note: This is not complete. It is ok for now as this 
     * driver is built into the kernel. */

    int unit = 0;
    
    for (unit = 0; unit < 1; unit++)
    {
        printk("vlynq_cleanup_module(): Unregistring misc device %s\n",vlynq_miscdev[unit].name);
        misc_deregister(&vlynq_miscdev[unit]);
    }

    remove_proc_entry("avalanche/vlynq_link", NULL);
    remove_proc_entry("avalanche/vlynq_ver", NULL);
}

module_init(vlynq_init_module);
module_exit(vlynq_cleanup_module);

