#include <asm/uaccess.h>
#include <linux/capability.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/in.h>
#include <linux/init.h>
#include <linux/if.h>
#include <linux/if_ether.h>
#include "ptm_mii_sw_wanif.h"
#ifdef USE_PTM_MII_WANIF
#include "ptm_rtl8685.h"

#if 1
#define PTMMSG(format, args...) if(pt_wan_dbg) printk("%s:%d> " format, __FUNCTION__, __LINE__, ##args)
#else
#define PTMMSG(format, args...)	while(0){}
#endif


typedef struct _ptm_mii_wanif_sw_
{
	struct _ptm_mii_wanif_sw_ *next;
	unsigned char	name[IFNAMSIZ];
	unsigned char	addr[ETH_ALEN];
	unsigned short	svid;
	unsigned short	cvid;
	unsigned char	tag:4;
	unsigned char	isbridge:4;
	unsigned char	path;
	unsigned char	qid[8];
} ptm_mii_wanif_sw;
static ptm_mii_wanif_sw *pwaniflist=NULL;
static int pt_wan_dbg=0;




/*hw api*******************************************************************/
static void ptm_mii_cleanall_hw_wanif(void)
{
	int i;
	rtl8685_WANtbl_t w;

	PTMMSG("\n");
	memset( &w, 0, sizeof(w) );
	for(i=0;i<PTM_MAX_INTF;i++)
	{
		rtl8685_setWanTable(i, &w );
	}
}

static void ptm_mii_update_hw_wanif(int idx, ptm_mii_wanif_sw *n)
{
	rtl8685_WANtbl_t w;

	PTMMSG("\n");
	memset( &w, 0, sizeof(w) );
	w.Valid=1;
	w.SVlanID=n->svid;
	w.CVlanID=n->cvid;
	if(n->isbridge==0)
		memcpy( w.MAC, n->addr, ETH_ALEN );
	strncpy(w.ifname, n->name, IFNAMSIZ);
	rtl8685_setWanTable(idx, &w );
}

static void ptm_mii_update_hw_qmap(int idx, ptm_mii_wanif_sw *n)
{
	int max_qid=8;
	int qid;	

	PTMMSG("\n");	
	for(qid=0; qid<max_qid; qid++)
	{
		unsigned char qval;
		qval= ((n->path&0x1)<<2)|(n->qid[qid]&0x3);
		rtl8685_set_QMap(idx,qid,qval);
	}
}

static void ptm_mii_update_hw(void)
{
	int idx=0;
	ptm_mii_wanif_sw *c=pwaniflist;

	PTMMSG("\n");
	//set all invalid
	ptm_mii_cleanall_hw_wanif();
	while(c)
	{
		ptm_mii_update_hw_wanif(idx, c);
		ptm_mii_update_hw_qmap(idx, c);
		c=c->next;
		idx++;
	}
}
/*end hw api***************************************************************/


static int ptm_mii_gen_tag(ptm_mii_wanif_sw *n)
{
	int ret=-1;

	PTMMSG("\n");
	if(n)
	{
		ret=0;
		if(n->svid) ret+=4;
		if(n->cvid) ret+=2;
		if(n->isbridge==0) ret+=1;
	}

	return ret;
}

static void ptm_mii_dump_wanif_node(ptm_mii_wanif_sw *c)	
{
	PTMMSG("c=0x%08x\n", (unsigned int)c);
	if(c)
	{
		printk( "name=%s, tag=%u, isbridge=%u\n",
			c->name, c->tag, c->isbridge);
		printk( "\t svid=%u, cvid=%u, mac=%02x%02x%02x%02x%02x%02x\n",
			c->svid, c->cvid, c->addr[0],c->addr[1],
			c->addr[2],c->addr[3],c->addr[4],c->addr[5]);
		printk( "\t path=%u, qid=%u%u%u%u%u%u%u%u\n",
			c->path, c->qid[0], c->qid[1], c->qid[2], 
			c->qid[3], c->qid[4], c->qid[5], c->qid[6], c->qid[7]);
	}
}

static void ptm_mii_dump_wanif(void)
{
	ptm_mii_wanif_sw *c=pwaniflist;

	PTMMSG("enter: c=0x%08x\n", (unsigned int)c);
	while(c)
	{
		ptm_mii_dump_wanif_node(c);
		c=c->next;
	}
}

static int ptm_mii_get_wanif_num(void)
{
	ptm_mii_wanif_sw *c=pwaniflist;
	int idx=0;

	PTMMSG("enter: c=0x%08x\n", (unsigned int)c);
	while(c)
	{
		idx++;
		c=c->next;
	}

	return idx;
}

static int ptm_mii_insert_wanif(ptm_mii_wanif_sw *n)
{
	ptm_mii_wanif_sw **p=&pwaniflist;
	ptm_mii_wanif_sw *c=pwaniflist;

	PTMMSG("enter: n=0x%08x\n", (unsigned int)n);
	if(n==NULL) return -1;

	PTMMSG("p=0x%08x, c=0x%08x\n", (unsigned int)p, (unsigned int)c);
	PTMMSG("n->name=%s, n->tag=%u\n", n->name, n->tag);
	//check duplicate
	while( c )
	{
		PTMMSG("c->name=%s, c->tag=%u\n", c->name, c->tag);
		if( c->tag>=n->tag )
		{
			p=&c->next;
			c=c->next;
			PTMMSG("p=0x%08x, c=0x%08x\n", (unsigned int)p, (unsigned int)c);
		}else
			break;
	}

	n->next=c;
	*p=n;	
	ptm_mii_update_hw();

	if(pt_wan_dbg) ptm_mii_dump_wanif();
	return 0;
}

static ptm_mii_wanif_sw *ptm_mii_get_wanif(unsigned char *name, int *idx)
{
	ptm_mii_wanif_sw *c=pwaniflist;
	int i=0;

	PTMMSG("enter: name=%s\n", name?name:(unsigned char*)"" );
	if(name==NULL) return NULL;
	if(idx) *idx=-1;

	PTMMSG("c=0x%08x\n", (unsigned int)c);
	while( c )
	{
		PTMMSG("c->name=%s\n", c->name);
		if( strncmp(c->name,name,IFNAMSIZ) )
		{
			c=c->next;			
			PTMMSG("c=0x%08x\n", (unsigned int)c);
		}else{
			PTMMSG("match, c=0x%08x\n", (unsigned int)c);
			if(idx) *idx=i;
			break;
		}

		i++;
	}

	return c;
}

static ptm_mii_wanif_sw *ptm_mii_remove_wanif(unsigned char *name)
{
	ptm_mii_wanif_sw **p=&pwaniflist;
	ptm_mii_wanif_sw *c=pwaniflist;

	PTMMSG("enter: name=%s\n", name?name:(unsigned char*)"" );
	if(name==NULL) return NULL;

	PTMMSG("p=0x%08x, c=0x%08x\n", (unsigned int)p, (unsigned int)c);
	while( c )
	{
		PTMMSG("c->name=%s\n", c->name);
		if( strncmp(c->name,name,IFNAMSIZ) )
		{
			p=&c->next;
			c=c->next;			
			PTMMSG("p=0x%08x, c=0x%08x\n", (unsigned int)p, (unsigned int)c);
		}else{
			*p=c->next;
			c->next=NULL;			
			PTMMSG("match, p=0x%08x, c=0x%08x\n", (unsigned int)p, (unsigned int)c);
			break;
		}
	}
	
	if(pt_wan_dbg) ptm_mii_dump_wanif();
	return c;
}

static int ptm_mii_delete_wanif(unsigned char	*name)
{
	ptm_mii_wanif_sw *c;

	c=ptm_mii_remove_wanif(name);
	if(c)
	{
		PTMMSG( "free node=0x%08x\n", (unsigned int)c );
		kfree(c);
		ptm_mii_update_hw();
		return 0;
	}

	return -1;
}

static void ptm_mii_debug(char *name, unsigned char path, unsigned char *qid)
{
	//start from 200
	switch(path)
	{
	case 200:
		pt_wan_dbg=0;
		printk( "pt_wan_dbg disabled\n");
		break;
	case 201:
		pt_wan_dbg=1;
		printk( "pt_wan_dbg enabled\n");
		break;
	case 202:
		{
			int num;
			num=ptm_mii_get_wanif_num();
			printk( "total ptm mii wan interface = %d\n", num );
			ptm_mii_dump_wanif();
			printk( "\n" );
		}
		break;
	default:
		break;
	}
}


/*smux-related api***************************************************************/
int ptm_mii_update_qmap(char *name, unsigned char path, unsigned char *qid)
{
	unsigned long flags;
	int ret=-1;
	ptm_mii_wanif_sw *n;
	int idx;

	PTMMSG("name=%s, path=%u\n", name?name:"", path);
	if(qid) PTMMSG("qid=%u%u%u%u%u%u%u%u\n", 
			qid[0],qid[1],qid[2],qid[3],qid[4],qid[5],qid[6],qid[7]);
	
	if( (name==NULL) || (qid==NULL) )
		return ret;

	//for debug
	if(path>=200)
	{
		ptm_mii_debug( name, path, qid );
		return 0;
	}

	local_irq_save(flags);
	n=ptm_mii_get_wanif(name, &idx);
	if(n)
	{
		n->path=path?1:0;
		memcpy( n->qid, qid, sizeof(n->qid));
		ptm_mii_update_hw_qmap(idx, n);
		ret=0;
	}	
	local_irq_restore(flags);
	
	return ret;
}


int ptm_mii_update_wanif_macaddr(char *name, unsigned char *addr)
{
	unsigned long flags;
	int ret=-1;
	ptm_mii_wanif_sw *n;
	int idx;

	PTMMSG("name=%s\n", name?name:"");
	if(addr) PTMMSG("addr=%02x%02x%02x%02x%02x%02x\n", addr[0],addr[1],addr[2],addr[3],addr[4],addr[5]);
	
	if( (name==NULL) || (addr==NULL) )
		return ret;

	local_irq_save(flags);
	n=ptm_mii_get_wanif(name, &idx);
	if(n)
	{
		if( (!n->isbridge)&&
			(!addr[0])&&(!addr[1])&&(!addr[2])&&
			(!addr[3])&&(!addr[4])&&(!addr[5]) )
		{
			printk( "%s: error (not bridge but macaddr=0)\n", __FUNCTION__ );
		}else{
			memcpy( n->addr, addr, ETH_ALEN);
			if(!n->isbridge)
				ptm_mii_update_hw_wanif(idx, n);
			ret=0;
		}
	}
	local_irq_restore(flags);
	
	return ret;
}


int ptm_mii_register_wanif(char *name, unsigned char *addr, int svid, int cvid, int isbridge)
{
	unsigned long flags;
	int ret=-1;
	ptm_mii_wanif_sw *n;
	
	PTMMSG("name=%s, isbridge=%d, svid=%d, cvid=%d, addr=0x%08x\n", 
		name?name:"", isbridge, svid, cvid, (unsigned int)addr);
	if(addr) PTMMSG("addr=%02x%02x%02x%02x%02x%02x\n", 
				addr[0],addr[1],addr[2],addr[3],addr[4],addr[5]);

	if( (name==NULL) || (addr==NULL) )
		return ret;

	if(ptm_mii_get_wanif_num()>=PTM_MAX_INTF)
	{
		printk( "%s: error (exceed the ptm wanif num, %d)\n", __FUNCTION__, PTM_MAX_INTF );
		return ret;
	}

	if( (!isbridge)&&
		(!addr[0])&&(!addr[1])&&(!addr[2])&&
		(!addr[3])&&(!addr[4])&&(!addr[5]) )
	{
		printk( "%s: error (not bridge but macaddr=0)\n", __FUNCTION__ );
		return ret;
	}

	n=(ptm_mii_wanif_sw *)kmalloc(sizeof(ptm_mii_wanif_sw) , GFP_KERNEL);
	if(n==NULL) return ret;
	PTMMSG("new node=0x%08x\n", (unsigned int)n);

	memset( n, 0, sizeof(ptm_mii_wanif_sw) );
	strncpy( n->name, name, IFNAMSIZ );
	memcpy( n->addr, addr, ETH_ALEN);
	if(svid<0) svid=0;
	n->svid=(unsigned short)svid&0xfff;
	if(cvid<0) cvid=0;
	n->cvid=(unsigned short)cvid&0xfff;
	n->isbridge=isbridge?1:0;
	n->path=1; //fast
	n->tag=ptm_mii_gen_tag(n);
	
	local_irq_save(flags);
	ret=ptm_mii_insert_wanif(n);
	local_irq_restore(flags);
	if(ret<0)
	{
		PTMMSG( "free node=0x%08x\n", (unsigned int)n );
		kfree(n);
	}
	
	return ret;
}


int ptm_mii_unregister_wanif(char *name)
{
	unsigned long flags;
	int ret=-1;
	
	PTMMSG( "enter, name=%s\n", name?name:"" );
	if(name==NULL) return ret;
	
	local_irq_save(flags);
	ret=ptm_mii_delete_wanif(name);
	local_irq_restore(flags);
	
	return ret;
}
/*end smux-related api***************************************************************/


#if 0
int ptm_mii_main(void)
//int main(void)
{
	int i=0;
	unsigned char testmac1[6]={0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
	//unsigned char testmac1[6]={0, 0, 0, 0, 0, 0};
	unsigned char testmac2[6]={0x00, 0x11, 0x22, 0x33, 0x44, 0x66};
	unsigned char testmac3[6]={0x00, 0x11, 0x22, 0x33, 0x44, 0x77};
	unsigned char testmac4[6]={0x00, 0x11, 0x22, 0x33, 0x44, 0x44};
	unsigned char testmac5[6]={0x11, 0x22, 0x33, 0x44, 0x77, 0x88};
	unsigned char qid[8]={ 0,0,1,1,2,2,3,3 };


	PTMMSG( "%d=================================\n", i++ );
	ptm_mii_register_wanif( "ptm0", testmac1, 100, 100, 0 );

	PTMMSG( "%d=================================\n", i++ );
	ptm_mii_register_wanif( "ptm1", testmac2, 0, 0, 1 );

	PTMMSG( "%d=================================\n", i++ );
	ptm_mii_register_wanif( "ptm2", testmac3, 0,0, 0 );

	PTMMSG( "%d=================================\n", i++ );
	ptm_mii_register_wanif( "ptm3", testmac4, 200,200, 1 );


	PTMMSG( "%d=================================\n", i++ );
	ptm_mii_dump_wanif();
	ptm_mii_update_qmap( "ptm3", 1, qid );
	ptm_mii_dump_wanif();

	PTMMSG( "%d=================================\n", i++ );
	ptm_mii_update_wanif_macaddr( "ptm2", testmac4 );


	PTMMSG( "%d=================================\n", i++ );
	ptm_mii_unregister_wanif( "ptm99" );
	
	PTMMSG( "%d=================================\n", i++ );
	ptm_mii_unregister_wanif( "ptm1" );

	PTMMSG( "%d=================================\n", i++ );
	ptm_mii_unregister_wanif( "ptm0" );

	PTMMSG( "%d=================================\n", i++ );
	ptm_mii_unregister_wanif( "ptm2" );

	PTMMSG( "%d=================================\n", i++ );
	ptm_mii_unregister_wanif( "ptm3" );

	PTMMSG( "%d=================================\n", i++ );
	return 0;
}
#endif

#endif /*USE_PTM_MII_WANIF*/