/*
*	Copyright. All rights is diminished
*
*	$Author: August 2011-10-20
*
*
*	Ethtool for adsl-switch-0412
*/

#include "rtl.h"

#ifdef CONFIG_RTL_MULTI_LAN_DEV

/* source back-compat hooks */
#define SET_ETHTOOL_OPS(netdev,ops) \
		( (netdev)->ethtool_ops = (ops) )

static struct {
	const char str[ETH_GSTRING_LEN];
} ethtool_stats_keys[] = {
	{ "rx_octets" },
	{ "rx_discards" },
	{ "rx_ucast_packets" },
	{ "rx_mcast_packets" },
	{ "rx_bcast_packets" },
	{ "tx_octets" },
	{ "tx_discards" },
	{ "tx_ucast_packets" },
	{ "tx_mcast_packets" },
	{ "tx_bcast_packets" },
};

/****************************************
 * check whether the port is detected
 *
 *  user output: Link detected: yes/no
****************************************/
static u32 rtl_ethtool_get_port_link(struct net_device *dev)
{
	uint32  portnum = 0;
	uint32	regData;

	//AUG_DBG("dev: %s\n", dev->name);
	//the user space has check that whether dev exist.
//	DEV_TO_PORT(dev, portnum);
	portnum = ethtool_get_port_by_dev(dev);

	regData = READ_MEM32(PSRP0+((portnum)<<2));

	return (regData & PortStatusLinkUp);		
}


/************************************************
 * get the Speed, Link Mode, Duplex, 
 *				N-way(Auto-neg), etc.
 *
 *
************************************************/
static int rtl_ethtool_get_settings(struct net_device *dev, struct ethtool_cmd *ecmd)
{
	uint32	regData;
	uint32	portnum = 0;
	uint32  tmp_spd;
	uint32  phycap = 0;
	uint32 	phystatus = 0;

	portnum = ethtool_get_port_by_dev(dev);
	
	/*read supported speed, duplex, and ability*/
	rtl8651_getAsicEthernetPHYSupportedAbility(portnum, &phycap);
	rtl8651_getAsicEthernetPHYStatus(portnum, &phystatus);

	ecmd->supported = 0;
	/*set supported speed and duplex*/
	if (phycap & (1 << HALF_DUPLEX_10M)) {
		ecmd->supported |= SUPPORTED_10baseT_Half;
	}
	if (phycap & (1 << DUPLEX_10M)) {
		ecmd->supported |= SUPPORTED_10baseT_Full;
	}	
	if (phycap & (1 << HALF_DUPLEX_100M)) {
		ecmd->supported |= SUPPORTED_100baseT_Half;
	}
	if (phycap & (1 << DUPLEX_100M)) {
		ecmd->supported |= SUPPORTED_100baseT_Full;
	}
	if (phycap & (1 << HALF_DUPLEX_1000M)) {
		ecmd->supported |= SUPPORTED_1000baseT_Half;
	}
	if (phycap & (1 << DUPLEX_1000M)) {
		ecmd->supported |= SUPPORTED_1000baseT_Full;
	}

	/*set supported ability*/
	if (phystatus & STS_CAPABLE_NWAY_AUTONEGO) {
		ecmd->supported |= SUPPORTED_Autoneg;
	}
		                   
	ecmd->supported |= (SUPPORTED_TP |SUPPORTED_Pause);

	/*read advertising speed and duplex*/
	rtl8651_getAsicEthernetPHYAdvCapability(portnum, &phycap);

	ecmd->advertising = 0;
	/*set supported speed and duplex*/
	if (phycap & (1 << HALF_DUPLEX_10M)) {
		ecmd->advertising |= SUPPORTED_10baseT_Half;
	}
	if (phycap & (1 << DUPLEX_10M)) {
		ecmd->advertising |= SUPPORTED_10baseT_Full;
	}	
	if (phycap & (1 << HALF_DUPLEX_100M)) {
		ecmd->advertising |= SUPPORTED_100baseT_Half;
	}
	if (phycap & (1 << DUPLEX_100M)) {
		ecmd->advertising |= SUPPORTED_100baseT_Full;
	}
	if (phycap & (1 << HALF_DUPLEX_1000M)) {
		ecmd->advertising |= SUPPORTED_1000baseT_Half;
	}
	if (phycap & (1 << DUPLEX_1000M)) {
		ecmd->advertising |= SUPPORTED_1000baseT_Full;
	}

	ecmd->advertising |= (ADVERTISED_Autoneg | ADVERTISED_Pause | ADVERTISED_TP);


	/*read port status register*/
	regData = READ_MEM32(PSRP0+((portnum)<<2));

	ecmd->port = PORT_TP;
	ecmd->autoneg = (regData & PortStatusNWayEnable) ? AUTONEG_ENABLE : AUTONEG_DISABLE;
	ecmd->duplex = (regData & PortStatusDuplex) ? DUPLEX_FULL : DUPLEX_HALF;
	ecmd->transceiver = XCVR_INTERNAL;
	ecmd->phy_address = portnum;

	tmp_spd = regData & PortStatusLinkSpeed_MASK;

	//if the port is not linked, we set the speed as "UNKOWN!".
	if(rtl_ethtool_get_port_link(dev))
		ecmd->speed = tmp_spd==PortStatusLinkSpeed100M ? 
								 SPEED_100 : ( tmp_spd==PortStatusLinkSpeed1000M? SPEED_1000 : SPEED_10);
	else
		ecmd->speed = 0;

	return 0;	
}

#define SPEED10M 	0
#define SPEED100M 	1
#define SPEED1000M 	2
#define EMB_PHY_ID 	0xbb804050 
static int rtl_ethtool_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
{
	int port;
	unsigned int phyid;
	int forceLinkSpeed = 0;
	uint32 phyCap = 0;
		
	port = ethtool_get_port_by_dev(dev);
	if (port < 0)
		return -ENODEV;
	
	//printk("%s(%d) cmd->autoneg = %d, cmd->speed = %d, cmd->duplex = %d\n", __func__, __LINE__, cmd->autoneg, cmd->speed, cmd->duplex);
		
	phyid = (REG32(EMB_PHY_ID) >> (port*5)) & 0x1f;

	if (cmd->autoneg) {
		switch (cmd->speed) {
			case SPEED_1000: 
				phyCap |= ((1 << HALF_DUPLEX_1000M) | ((cmd->duplex)? (1 << DUPLEX_1000M):0)); 
				forceLinkSpeed = SPEED1000M;
			case SPEED_100: 
				phyCap |= ((1 << HALF_DUPLEX_100M) | ((cmd->duplex)? (1 << DUPLEX_100M):0));  
				forceLinkSpeed = ((forceLinkSpeed > SPEED100M)? forceLinkSpeed:SPEED100M);		
			case SPEED_10: 
				phyCap |= ((1 << HALF_DUPLEX_10M) | ((cmd->duplex)? (1 << DUPLEX_10M):0));
				forceLinkSpeed = ((forceLinkSpeed > SPEED10M)? forceLinkSpeed:SPEED10M);
				break;
		default:
			return -EINVAL;
		}
		}
	else {
		switch (cmd->speed) {
			case SPEED_1000: 
				phyCap |= (cmd->duplex)? (1 << DUPLEX_1000M):(1 << HALF_DUPLEX_1000M);
				forceLinkSpeed = SPEED1000M;
				break;
			case SPEED_100: 
				phyCap |= (cmd->duplex)? (1 << DUPLEX_100M):(1 << HALF_DUPLEX_100M);
				forceLinkSpeed = SPEED100M;
				break;
			case SPEED_10: 
				phyCap |= (cmd->duplex)? (1 << DUPLEX_10M):(1 << HALF_DUPLEX_10M);
				forceLinkSpeed = SPEED10M;
				break;
			default:
				return -EINVAL;
		}
		
	}

	//printk("%s(%d): autoneg = %s, phyCap = 0x%02x\n", __func__, __LINE__, cmd->autoneg?"on":"off", phyCap);
	rtl865xC_setAsicEthernetForceModeRegs(port, !cmd->autoneg, 1, forceLinkSpeed, cmd->duplex);

	if (cmd->autoneg) {
		rtl8651_setAsicEthernetPHYNwayAbility(port, phyCap);
	}
	else {
		rtl8651_setAsicEthernetPHYSpeed(port, forceLinkSpeed);
	}
	
	rtl8651_setAsicEthernetPHYDuplex(port, phyCap);
	rtl8651_setAsicEthernetPHYAutoNeg(port, cmd->autoneg);
	rtl8651_setAsicEthernetPHYAdvCapability(port, phyCap);
	rtl8651_restartAsicEthernetPHYNway(port);
	return 	0;
}

/************************************************
 * get the pauseparam.
 *
 * shell output:
 		# ethtool --show-pause eth0.2
			Pause parameters for eth0.2:
			Autonegotiate:  on
			RX:             off
			TX:             off
 *
************************************************/
static void rtl_ethtool_get_pauseparam(struct net_device *dev, struct ethtool_pauseparam *pause)
{
	uint32	regData;
	uint32	portnum = 0;

	//DEV_TO_PORT(dev, portnum);
	portnum = ethtool_get_port_by_dev(dev);
	regData = READ_MEM32(PSRP0+((portnum)<<2));

	pause->autoneg = (regData & PortStatusNWayEnable) ? AUTONEG_ENABLE : AUTONEG_DISABLE;

	pause->rx_pause = (regData & PortStatusRXPAUSE) ? 1 : 0;
	pause->tx_pause = (regData & PortStatusTXPAUSE) ? 1 : 0;
	
}

static int rtl_ethtool_set_pauseparam(struct net_device *dev, struct ethtool_pauseparam *pause)
{
	uint32 port;
	uint32 pauseFC = 0;
	uint32 offset;

	port = ethtool_get_port_by_dev(dev);

	offset = port << 2;

	if (pause->rx_pause && pause->tx_pause)
		pauseFC |= PauseFlowControlEtxErx;
	else if (pause->rx_pause && !pause->tx_pause)
		pauseFC |= PauseFlowControlDtxErx;
	else if (!pause->rx_pause && pause->tx_pause)
		pauseFC |= PauseFlowControlEtxDrx;
	else if (!pause->rx_pause && !pause->tx_pause)
		pauseFC = 0;

	WRITE_MEM32(PCRP0+offset, (~(PauseFlowControl_MASK)&(READ_MEM32(PCRP0+offset)))|pauseFC);

	TOGGLE_BIT_IN_REG_TWICE(PCRP0 + offset,EnForceMode);

	rtl8651_restartAsicEthernetPHYNway(port);

	return 0;
}

static void rtl_ethtool_get_strings(struct net_device *dev, u32 stringset, u8 *data)
{
	memcpy(data, ethtool_stats_keys, sizeof(ethtool_stats_keys));
}

static int rtl_ethtool_get_sset_count(struct net_device *dev, int sset)
{
	switch (sset) {
	case ETH_SS_STATS:
		return NR_ASIC_DIAG_COUNTER_USED;
	default:
		return -EOPNOTSUPP;
	}
}

static void rtl_ethtool_get_ethtool_stats(struct net_device *dev,
					  struct ethtool_stats *stats,
					  u64 * data)
{
	int idx = 0;
	const uint32 addrOffset_fromP0 = ethtool_get_port_by_dev(dev) * MIB_ADDROFFSETBYPORT;

	data[idx++] = rtl865xC_returnAsicCounter64(OFFSET_IFINOCTETS_P0 +
				      addrOffset_fromP0);
	data[idx++] = rtl8651_returnAsicCounter(OFFSET_DOT1DTPPORTINDISCARDS_P0 +
				      addrOffset_fromP0);
	data[idx++] = rtl8651_returnAsicCounter(OFFSET_IFINUCASTPKTS_P0 +
				      addrOffset_fromP0);
	data[idx++] = rtl8651_returnAsicCounter(OFFSET_ETHERSTATSMULTICASTPKTS_P0 +
				      addrOffset_fromP0);
	data[idx++] = rtl8651_returnAsicCounter(OFFSET_ETHERSTATSBROADCASTPKTS_P0 +
				      addrOffset_fromP0);
	data[idx++] = rtl865xC_returnAsicCounter64(OFFSET_IFOUTOCTETS_P0 +
				      addrOffset_fromP0);
	data[idx++] = rtl8651_returnAsicCounter(OFFSET_IFOUTDISCARDS +
				      addrOffset_fromP0);
	data[idx++] = rtl8651_returnAsicCounter(OFFSET_IFOUTUCASTPKTS_P0 +
				      addrOffset_fromP0);
	data[idx++] = rtl8651_returnAsicCounter(OFFSET_IFOUTMULTICASTPKTS_P0 +
				      addrOffset_fromP0);
	data[idx++] = rtl8651_returnAsicCounter(OFFSET_IFOUTBROADCASTPKTS_P0 +
				      addrOffset_fromP0);
}

static void rtl_ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *drvinfo)
{
	/*printk("\033[9m %s \033[m\n", "RTL Rights Reserved!");*/
}

static const struct ethtool_ops rtl_ethtool_ops = {
	.get_link		= ethtool_op_get_link,//rtl_ethtool_get_port_link,
	.get_drvinfo		= rtl_ethtool_get_drvinfo,
	.get_settings		= rtl_ethtool_get_settings,
	.set_settings       	= rtl_ethtool_set_settings,
	.get_pauseparam		= rtl_ethtool_get_pauseparam,
	.set_pauseparam		= rtl_ethtool_set_pauseparam,
	.get_strings		= rtl_ethtool_get_strings,
	.get_sset_count		= rtl_ethtool_get_sset_count,
	.get_ethtool_stats	= rtl_ethtool_get_ethtool_stats,
};

void rtl_set_ethtool_ops(struct net_device *netdev)
{
	SET_ETHTOOL_OPS(netdev, &rtl_ethtool_ops);
}



#endif
//make wrappers of netlink funcs and make a protocol of kernel-2-user communication

struct sock *global_rtlmsg_sock;

#ifdef CONFIG_RTL_NLMSG_PROTOCOL
void rtl_nlmsg_handler(struct sk_buff *skb)
{
	printk("Nothing to do!!!\n");
	return;
}


int rtl_gbl_rtlmsg_sock_init(void)
{
	global_rtlmsg_sock = netlink_kernel_create(&init_net, NETLINK_RTL_NLMSG, 0, rtl_nlmsg_handler, NULL, THIS_MODULE);
    if(!global_rtlmsg_sock)
    {
        printk("Fail to create netlink socket.\n");
        return -1;
    }
	return 0;
}


struct sk_buff* rtl_nlmsg_alloc_skb(const RTL_ST_NLMSG* p_nlmsg)
{
	int len;
	struct sk_buff  *skb;

	len = NLMSG_SPACE(RTL_NLMSG_HEAD_SZ + p_nlmsg->length);

	skb = alloc_skb(len, GFP_ATOMIC);
	
	return skb;
}

void rtl_nlmsg_set_skb(struct sk_buff *skb, const RTL_ST_NLMSG* p_nlmsg, u32 pid, u32 seq)
{
	struct nlmsghdr *nlhdr;
	int len;

	RTL_ST_NLMSG* tmp_nlmsg;
	
	len = NLMSG_SPACE(RTL_NLMSG_HEAD_SZ + p_nlmsg->length);

	nlhdr = __nlmsg_put(skb, pid, seq, 0, (len - sizeof(struct nlmsghdr)), 0);

	tmp_nlmsg = (RTL_ST_NLMSG *)(NLMSG_DATA(nlhdr));

	memcpy(tmp_nlmsg, p_nlmsg, RTL_NLMSG_HEAD_SZ + p_nlmsg->length);

	return;
}

int rtl_nlmsg_broadcast(struct sock *ssk, struct sk_buff *skb, RTL_NL_GRPS dest_grp, gfp_t allocation)
{
	//august : the param 'dest_group' is uesd strangly, but there it is.
	//august : I 've checked linux version from 2.6.30 to 3.1.0, the usage and codes do not change! 
	//august : this will be sended to linux-dev-mailist.
	return  netlink_broadcast(ssk, skb, 0, (1 << (dest_grp - 1)), GFP_KERNEL);
}

/***@@@@@@@@@@@%%%%%%%%%#########
	
	SERIOUS NOTE: 	This func is called in the bottom half func.

@@@@@@@@@@@%%%%%%%%%#########***/

int rtl_nl_send_lkchg_msg(const unsigned int old_ptmks, const unsigned int new_ptmks)
{
	struct sk_buff  *skb;

	RTL_NLMSG_LINKCHG link;

	link.rtl_magic 	= 	RTL_NL_SRC_KENERL | RTL_NL_MAGIC_NUM;

	link.type		=	RTL_NL_TYPE_LINKCHANGE;
	link.length		=	EXTSIZE_RTL_NLMSG_LINKCHG;

	link.extended.new_ptmks = new_ptmks;
	link.extended.old_ptmks = old_ptmks;

	skb = rtl_nlmsg_alloc_skb((RTL_ST_NLMSG*)&link);
	if(!skb)
	{
		printk("alloc skb failed!\n");
		return -55;
	}

	rtl_nlmsg_set_skb(skb, (RTL_ST_NLMSG*)&link, 0, 0);

	return rtl_nlmsg_broadcast(global_rtlmsg_sock, skb, RTL_BASIC_GRP, GFP_KERNEL);

}

#endif