aboutsummaryrefslogblamecommitdiffstats
blob: a84d5d78623cbc00359701fd96efcf59e40f574d (plain) (tree)






























































                                                                      


                                                   
                             
                                               


                                                          




                                            







                                                      




                                                 
                                                          

                                                    


                     









                                                        
                                                    



















































































































































































                                                                                

                                                         
 

                                                    













                                                              


                                                    
                                                   
                                                      
                                                
                                               

  

                                                                 














                                                
                                            










































































































                                                                            

                                          



























                                                      



















                                                                              


                                                        







                                                                         

                                                                 




                                                                         

                                                 

         






                                                                      
































































































































































































































































































































































                                                                               







                                                                         










































                                                                              





                                                          

























                                                                           





                                                          












                                                          
                                      


















































































































































































































                                                                              




                                                                           

                           







                                                   
                                   

                                
                                   

                                
                                    
                      


                                                                             




                                                   

                                                      















                                                                
                                               
                                                 
                                                 









































                                                                              









































                                                                            




















































                                                                            

                                  









                                                   
                                           

                                        
                                           

                                        
                                           
                              



                                                                          

                 

                                              


                                                                    
                         















                                                                            
                   
                                                   
                                               
                                                     
         



               
























































                                                                                







                                                             

                                                        































                                                                             


                        



                                                 
                                                           

































                                                                             
                                                                    
            
                                                                     






























                                                                       


                                                    

                                                         

                                                            

                                                                      

















                                                                               
                                  


                                                                




                                                            
                                                                      
                                                               












                                                                   
                                          













                                                                              









                                                                     
                                                                   



                                                          
                                             



                                                                 
                                               












                                                                         




                                                                              





                                                             
                                                     

                                                             
                                          
 
                                                   
 




                                                                   
 




                                                                          
 


                                                               
 

                                                          
 
                                                                    
 
                                                                

                                                                     
                                              


                                                          
                                              


                                                          
                                              


                                                              
                                              

                                                      













                                                             













                                                                               
 

                                                                          




                                                                       
 
                                                                           




                                                                       
 
                                                         




                                                                       
 
                                                                           






                                                                    


                              

























                                                                     


                              




















                                                                       




                                                                   
                



                                   
                                           

                                                       









                                                                
                                                                     


                               
                                       


                                                                               
                                       


                                                                             


                                                    

                                                              











                                                                          
                                                          
                    
                                                           

         

                                                         
                                           
                                              





















                                                                           
































                                                                               




                                                            
                                      

                                                         







                                                             
                                                            


                                                              



                                                                           
                                                                   
























                                                                            
                             
                 


















                                                                   

                                                             




                                                            






                                                                       
                             
































                                                                           
                                                               

         


                                                                



                 




































                                                                               










                                                                     


                 






















































































































































                                                                                
                                                       













                                                                  

                                                       









                                                              

                                                                













                                                                               

                                                               

                                  













                                                                              
                                    
                                                 
 



                                                  









                                           































                                                                     

































































































                                                                                


                                                                  



























                                                      















                                                            
/*
 * Copyright (C) 2012 Texas Instruments Incorporated
 * Authors: Sandeep Paulraj <s-paulraj@ti.com>
 * Authors: WingMan Kwok <w-kwok2@ti.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation version 2.
 *
 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
 * kind, whether express or implied; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#include <linux/io.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/phy.h>
#include <linux/timer.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/if_vlan.h>
#include <linux/of_mdio.h>
#include <linux/ethtool.h>
#include <linux/if_ether.h>
#include <linux/net_tstamp.h>
#include <linux/netdevice.h>
#include <linux/interrupt.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/scatterlist.h>
#include <linux/etherdevice.h>
#include <linux/platform_device.h>

#include "cpsw_ale.h"
#include "keystone_net.h"

#define CPSW_MODULE_NAME	"keystone-cpswx"
#define NETCP_DRIVER_NAME	"TI KeyStone XGE Driver"
#define NETCP_DRIVER_VERSION	"v0.0.0"

#define CPSW_IDENT(reg)			((reg >> 16) & 0xffff)
#define CPSW_MAJOR_VERSION(reg)		(reg >> 8 & 0x7)
#define CPSW_MINOR_VERSION(reg)		(reg & 0xff)
#define CPSW_RTL_VERSION(reg)		((reg >> 11) & 0x1f)

#define DEVICE_N_GMACSL_PORTS			2
#define DEVICE_EMACSL_RESET_POLL_COUNT		100

#define	CPSW_TIMER_INTERVAL			(HZ / 10)

/* Soft reset register values */
#define SOFT_RESET_MASK				BIT(0)
#define SOFT_RESET				BIT(0)

#define MACSL_RX_ENABLE_CSF			BIT(23)
#define MACSL_RX_ENABLE_EXT_CTL			BIT(18)
#define MACSL_XGMII_ENABLE			BIT(13)
#define MACSL_XGIG_MODE				BIT(8)
#define MACSL_GMII_ENABLE			BIT(5)
#define GMACSL_RET_WARN_RESET_INCOMPLETE	-2

#define SLAVE_LINK_IS_XGMII(s) \
	((s)->link_interface >= XGMII_LINK_MAC_PHY)

#define MACSL_SIG_ENABLE(s) \
		(SLAVE_LINK_IS_XGMII((s)) ?   \
		(MACSL_XGMII_ENABLE | MACSL_XGIG_MODE) : \
		MACSL_GMII_ENABLE)

#define MACSL_DEFAULT_CONFIG(s) \
		(MACSL_SIG_ENABLE((s)) | \
		 MACSL_RX_ENABLE_EXT_CTL | \
		 MACSL_RX_ENABLE_CSF)

#define CPSW_NUM_PORTS		                3
#define CPSW_CTL_P0_ENABLE			BIT(2)
#define CPSW_CTL_VLAN_AWARE			BIT(1)
#define CPSW_REG_VAL_STAT_ENABLE_ALL		0xf

#define CPSW_MASK_ALL_PORTS			7
#define CPSW_MASK_PHYS_PORTS			6
#define CPSW_MASK_NO_PORTS			0

#define CPSW_STATS0_MODULE			0
#define CPSW_STATS1_MODULE			1
#define CPSW_STATS2_MODULE			2

#define HOST_TX_PRI_MAP_DEFAULT			0x00000000
#define MAX_SIZE_STREAM_BUFFER		        9504

/* slave_num: 0-based
 *  port_num: 1-based
 */
struct cpswx_slave {
	struct cpswx_slave_regs __iomem		*regs;
	struct cpswx_sliver_regs __iomem	*sliver;
	int				 slave_num;
	int				 port_num;
	u32				 mac_control;
	struct phy_device		*phy;
	const char			*phy_id;
	struct cpsw_ale			*ale;
	u32				 link_interface;
	u8				 phy_port_t;
};

/* 0x0000 */
struct cpswx_ss_regs {
	u32	id_ver;
	u32	synce_count;
	u32	synce_mux;
	u32	control;
};

/* 0x1000 */
struct cpswx_regs {
	u32	id_ver;
	u32	control;
	u32	emcontrol;
	u32	stat_port_en;
	u32	ptype;
	u32	soft_idle;
	u32	thru_rate;
	u32	gap_thresh;
	u32	tx_start_wds;
	u32	flow_control;
	u32	cppi_thresh;
};

/* 0x1064, 0x1094 */
struct cpswx_slave_regs {
	u32	blk_cnt;
	u32	port_vlan;
	u32	tx_pri_map;
	u32	sa_lo;
	u32	sa_hi;
	u32	ts_ctl;
	u32	ts_seq_ltype;
	u32	ts_vlan;
	u32	ts_ctl_ltype2;
	u32	ts_ctl2;
	u32	control;
};

/* 0x1034 */
struct cpswx_host_regs {
	u32	blk_cnt;
	u32	port_vlan;
	u32	tx_pri_map;
	u32	src_id;
	u32	rx_pri_map;
	u32	rx_maxlen;
};

/* 0x1400, 0x1440 */
struct cpswx_sliver_regs {
	u32	id_ver;
	u32	mac_control;
	u32	mac_status;
	u32	soft_reset;
	u32	rx_maxlen;
	u32	__reserved_0;
	u32	rx_pause;
	u32	tx_pause;
	u32	em_control;
	u32	__reserved_1;
	u32	tx_gap;
	u32	rsvd[4];
};

struct cpswx_host_hw_stats {
	u32	rx_good_frames;
	u32	rx_broadcast_frames;
	u32	rx_multicast_frames;
	u32	__rsvd_0[3];
	u32	rx_oversized_frames;
	u32	__rsvd_1;
	u32	rx_undersized_frames;
	u32	__rsvd_2;
	u32	overrun_type4;
	u32	overrun_type5;
	u32	rx_bytes;
	u32	tx_good_frames;
	u32	tx_broadcast_frames;
	u32	tx_multicast_frames;
	u32	__rsvd_3[9];
	u32	tx_bytes;
	u32	tx_64byte_frames;
	u32	tx_65_to_127byte_frames;
	u32	tx_128_to_255byte_frames;
	u32	tx_256_to_511byte_frames;
	u32	tx_512_to_1023byte_frames;
	u32	tx_1024byte_frames;
	u32	net_bytes;
	u32	rx_sof_overruns;
	u32	rx_mof_overruns;
	u32	rx_dma_overruns;
};

/* 0x1900, 0x1a00 */
struct cpswx_hw_stats {
	u32	rx_good_frames;
	u32	rx_broadcast_frames;
	u32	rx_multicast_frames;
	u32	rx_pause_frames;
	u32	rx_crc_errors;
	u32	rx_align_code_errors;
	u32	rx_oversized_frames;
	u32	rx_jabber_frames;
	u32	rx_undersized_frames;
	u32	rx_fragments;
	u32	overrun_type4;
	u32	overrun_type5;
	u32	rx_bytes;
	u32	tx_good_frames;
	u32	tx_broadcast_frames;
	u32	tx_multicast_frames;
	u32	tx_pause_frames;
	u32	tx_deferred_frames;
	u32	tx_collision_frames;
	u32	tx_single_coll_frames;
	u32	tx_mult_coll_frames;
	u32	tx_excessive_collisions;
	u32	tx_late_collisions;
	u32	tx_underrun;
	u32	tx_carrier_sense_errors;
	u32	tx_bytes;
	u32	tx_64byte_frames;
	u32	tx_65_to_127byte_frames;
	u32	tx_128_to_255byte_frames;
	u32	tx_256_to_511byte_frames;
	u32	tx_512_to_1023byte_frames;
	u32	tx_1024byte_frames;
	u32	net_bytes;
	u32	rx_sof_overruns;
	u32	rx_mof_overruns;
	u32	rx_dma_overruns;
};

/* 0x1700 */
struct cpswx_ale_regs {
	u32	ale_idver;
	u32	__rsvd0;
	u32	ale_control;
	u32	__rsvd1;
	u32	ale_prescale;
	u32	ale_aging_timer;  /* +++FIXME: no description found in manual */
	u32	ale_unknown_vlan;
	u32	__rsvd3;
	u32	ale_tblctl;
	u32	__rsvd4[4];
	u32	ale_tblw2;
	u32	ale_tblw1;
	u32	ale_tblw0;
	u32	ale_portctl[3];
};

struct cpswx_priv {
	struct device			*dev;
	struct clk			*clk;
	struct netcp_device		*netcp_device;
	u32				 num_slaves;
	u32				 ale_ageout;
	u32				 ale_entries;
	u32				 ale_ports;
	u32				 sgmii_module_ofs;
	u32				 pcsr_module_ofs;
	u32				 switch_module_ofs;
	u32				 host_port_reg_ofs;
	u32				 slave_reg_ofs;
	u32				 sliver_reg_ofs;
	u32				 hw_stats_reg_ofs;
	u32				 ale_reg_ofs;

	int				 host_port;
	u32				 rx_packet_max;

	struct cpswx_regs __iomem		*regs;
	struct cpswx_ss_regs __iomem		*ss_regs;
	struct cpswx_host_hw_stats __iomem	*host_hw_stats_regs;
	struct cpswx_hw_stats __iomem		*hw_stats_regs[2];
	struct cpswx_host_regs __iomem		*host_port_regs;
	struct cpswx_ale_regs __iomem		*ale_reg;

	void __iomem			*sgmii_port_regs;
	void __iomem			*pcsr_port_regs;

	struct cpsw_ale			*ale;
	atomic_t			 ale_refcnt;

	u32				 link[5];
	struct device_node		*phy_node[4];

	u32				 intf_tx_queues;

	u32				 multi_if;
	u32				 slaves_per_interface;
	u32				 num_interfaces;
	struct device_node		*interfaces;
	struct list_head		 cpsw_intf_head;

	u64				 hw_stats[96];
	int				 init_serdes_at_probe;
	struct kobject			kobj;
	struct kobject			tx_pri_kobj;
	struct kobject			pvlan_kobj;
	struct kobject			stats_kobj;
	spinlock_t			hw_stats_lock;
	struct device_node              *serdes;
	u32				opened;
};

/* slave_port: 0-based (currently relevant only in multi_if mode)
*/
struct cpswx_intf {
	struct net_device	*ndev;
	struct device		*dev;
	struct cpswx_priv	*cpsw_priv;
	struct device_node	*phy_node;
	u32			 num_slaves;
	u32			 slave_port;
	struct cpswx_slave	*slaves;
	u32			 intf_tx_queues;
	const char		*tx_chan_name;
	u32			 tx_queue_depth;
	struct netcp_tx_pipe	 tx_pipe;
	u32			 multi_if;
	struct list_head	 cpsw_intf_list;
	struct timer_list	 timer;
	u32			 link_state;
};

/*
 * Statistic management
 */
struct netcp_ethtool_stat {
	char desc[ETH_GSTRING_LEN];
	int type;
	u32 size;
	int offset;
};

/* +++FIXME: do we need the port?? */
#define for_each_slave(priv, func, arg...)				\
	do {								\
		int idx, port;						\
		port = (priv)->slave_port;				\
		if ((priv)->multi_if)					\
			(func)((priv)->slaves, ##arg);			\
		else							\
			for (idx = 0; idx < (priv)->num_slaves; idx++)	\
				(func)((priv)->slaves + idx, ##arg);	\
	} while (0)

#define FIELDINFO(_struct, field) FIELD_SIZEOF(_struct, field), \
					offsetof(_struct, field)

#define CPSW_STATS0_INFO(field)	"CPSW_0:"#field, CPSW_STATS0_MODULE, \
				FIELDINFO(struct cpswx_host_hw_stats, field)

#define CPSW_STATSA_INFO(field)	"CPSW_1:"#field, CPSW_STATS1_MODULE, \
				FIELDINFO(struct cpswx_hw_stats, field)
#define CPSW_STATSB_INFO(field)	"CPSW_2:"#field, CPSW_STATS2_MODULE, \
				FIELDINFO(struct cpswx_hw_stats, field)

static const struct netcp_ethtool_stat et_stats[] = {
	/* CPSW module 0 */
	{CPSW_STATS0_INFO(rx_good_frames)},
	{CPSW_STATS0_INFO(rx_broadcast_frames)},
	{CPSW_STATS0_INFO(rx_multicast_frames)},
	{CPSW_STATS0_INFO(rx_oversized_frames)},
	{CPSW_STATS0_INFO(rx_undersized_frames)},
	{CPSW_STATS0_INFO(overrun_type4)},
	{CPSW_STATS0_INFO(overrun_type5)},
	{CPSW_STATS0_INFO(rx_bytes)},
	{CPSW_STATS0_INFO(tx_good_frames)},
	{CPSW_STATS0_INFO(tx_broadcast_frames)},
	{CPSW_STATS0_INFO(tx_multicast_frames)},
	{CPSW_STATS0_INFO(tx_bytes)},
	{CPSW_STATS0_INFO(tx_64byte_frames)},
	{CPSW_STATS0_INFO(tx_65_to_127byte_frames)},
	{CPSW_STATS0_INFO(tx_128_to_255byte_frames)},
	{CPSW_STATS0_INFO(tx_256_to_511byte_frames)},
	{CPSW_STATS0_INFO(tx_512_to_1023byte_frames)},
	{CPSW_STATS0_INFO(tx_1024byte_frames)},
	{CPSW_STATS0_INFO(net_bytes)},
	{CPSW_STATS0_INFO(rx_sof_overruns)},
	{CPSW_STATS0_INFO(rx_mof_overruns)},
	{CPSW_STATS0_INFO(rx_dma_overruns)},
	/* CPSW module 1 */
	{CPSW_STATSA_INFO(rx_good_frames)},
	{CPSW_STATSA_INFO(rx_broadcast_frames)},
	{CPSW_STATSA_INFO(rx_multicast_frames)},
	{CPSW_STATSA_INFO(rx_pause_frames)},
	{CPSW_STATSA_INFO(rx_crc_errors)},
	{CPSW_STATSA_INFO(rx_align_code_errors)},
	{CPSW_STATSA_INFO(rx_oversized_frames)},
	{CPSW_STATSA_INFO(rx_jabber_frames)},
	{CPSW_STATSA_INFO(rx_undersized_frames)},
	{CPSW_STATSA_INFO(rx_fragments)},
	{CPSW_STATSA_INFO(overrun_type4)},
	{CPSW_STATSA_INFO(overrun_type5)},
	{CPSW_STATSA_INFO(rx_bytes)},
	{CPSW_STATSA_INFO(tx_good_frames)},
	{CPSW_STATSA_INFO(tx_broadcast_frames)},
	{CPSW_STATSA_INFO(tx_multicast_frames)},
	{CPSW_STATSA_INFO(tx_pause_frames)},
	{CPSW_STATSA_INFO(tx_deferred_frames)},
	{CPSW_STATSA_INFO(tx_collision_frames)},
	{CPSW_STATSA_INFO(tx_single_coll_frames)},
	{CPSW_STATSA_INFO(tx_mult_coll_frames)},
	{CPSW_STATSA_INFO(tx_excessive_collisions)},
	{CPSW_STATSA_INFO(tx_late_collisions)},
	{CPSW_STATSA_INFO(tx_underrun)},
	{CPSW_STATSA_INFO(tx_carrier_sense_errors)},
	{CPSW_STATSA_INFO(tx_bytes)},
	{CPSW_STATSA_INFO(tx_64byte_frames)},
	{CPSW_STATSA_INFO(tx_65_to_127byte_frames)},
	{CPSW_STATSA_INFO(tx_128_to_255byte_frames)},
	{CPSW_STATSA_INFO(tx_256_to_511byte_frames)},
	{CPSW_STATSA_INFO(tx_512_to_1023byte_frames)},
	{CPSW_STATSA_INFO(tx_1024byte_frames)},
	{CPSW_STATSA_INFO(net_bytes)},
	{CPSW_STATSA_INFO(rx_sof_overruns)},
	{CPSW_STATSA_INFO(rx_mof_overruns)},
	{CPSW_STATSA_INFO(rx_dma_overruns)},
	/* CPSW module 2 */
	{CPSW_STATSB_INFO(rx_good_frames)},
	{CPSW_STATSB_INFO(rx_broadcast_frames)},
	{CPSW_STATSB_INFO(rx_multicast_frames)},
	{CPSW_STATSB_INFO(rx_pause_frames)},
	{CPSW_STATSB_INFO(rx_crc_errors)},
	{CPSW_STATSB_INFO(rx_align_code_errors)},
	{CPSW_STATSB_INFO(rx_oversized_frames)},
	{CPSW_STATSB_INFO(rx_jabber_frames)},
	{CPSW_STATSB_INFO(rx_undersized_frames)},
	{CPSW_STATSB_INFO(rx_fragments)},
	{CPSW_STATSB_INFO(overrun_type4)},
	{CPSW_STATSB_INFO(overrun_type5)},
	{CPSW_STATSB_INFO(rx_bytes)},
	{CPSW_STATSB_INFO(tx_good_frames)},
	{CPSW_STATSB_INFO(tx_broadcast_frames)},
	{CPSW_STATSB_INFO(tx_multicast_frames)},
	{CPSW_STATSB_INFO(tx_pause_frames)},
	{CPSW_STATSB_INFO(tx_deferred_frames)},
	{CPSW_STATSB_INFO(tx_collision_frames)},
	{CPSW_STATSB_INFO(tx_single_coll_frames)},
	{CPSW_STATSB_INFO(tx_mult_coll_frames)},
	{CPSW_STATSB_INFO(tx_excessive_collisions)},
	{CPSW_STATSB_INFO(tx_late_collisions)},
	{CPSW_STATSB_INFO(tx_underrun)},
	{CPSW_STATSB_INFO(tx_carrier_sense_errors)},
	{CPSW_STATSB_INFO(tx_bytes)},
	{CPSW_STATSB_INFO(tx_64byte_frames)},
	{CPSW_STATSB_INFO(tx_65_to_127byte_frames)},
	{CPSW_STATSB_INFO(tx_128_to_255byte_frames)},
	{CPSW_STATSB_INFO(tx_256_to_511byte_frames)},
	{CPSW_STATSB_INFO(tx_512_to_1023byte_frames)},
	{CPSW_STATSB_INFO(tx_1024byte_frames)},
	{CPSW_STATSB_INFO(net_bytes)},
	{CPSW_STATSB_INFO(rx_sof_overruns)},
	{CPSW_STATSB_INFO(rx_mof_overruns)},
	{CPSW_STATSB_INFO(rx_dma_overruns)},
};

#define ETHTOOL_STATS_NUM ARRAY_SIZE(et_stats)

struct cpswx_attribute {
	struct attribute attr;
	ssize_t (*show)(struct cpswx_priv *cpsw_dev,
		struct cpswx_attribute *attr, char *buf);
	ssize_t	(*store)(struct cpswx_priv *cpsw_dev,
		struct cpswx_attribute *attr, const char *, size_t);
	const struct cpswx_mod_info *info;
	ssize_t info_size;
	void *context;
};
#define to_cpswx_attr(_attr) container_of(_attr, struct cpswx_attribute, attr)

#define to_cpswx_dev(obj) container_of(obj, struct cpswx_priv, kobj)

#define tx_pri_to_cpswx_dev(obj) \
	container_of(obj, struct cpswx_priv, tx_pri_kobj)

#define pvlan_to_cpswx_dev(obj) \
	container_of(obj, struct cpswx_priv, pvlan_kobj)

#define stats_to_cpswx_dev(obj) \
	container_of(obj, struct cpswx_priv, stats_kobj)

#define BITS(x)			(BIT(x) - 1)
#define BITMASK(n, s)		(BITS(n) << (s))
#define cpsw_mod_info_field_val(r, i) \
	((r & BITMASK(i->bits, i->shift)) >> i->shift)

#define for_each_intf(i, priv) \
	list_for_each_entry((i), &(priv)->cpsw_intf_head, cpsw_intf_list)

#define __CPSW_ATTR_FULL(_name, _mode, _show, _store, _info,	\
				_info_size, _ctxt)		\
	{ \
		.attr = {.name = __stringify(_name), .mode = _mode },	\
		.show	= _show,		\
		.store	= _store,		\
		.info	= _info,		\
		.info_size = _info_size,	\
		.context = (_ctxt),		\
	}

#define __CPSW_ATTR(_name, _mode, _show, _store, _info) \
		__CPSW_ATTR_FULL(_name, _mode, _show, _store, _info, \
					(ARRAY_SIZE(_info)), NULL)

#define __CPSW_CTXT_ATTR(_name, _mode, _show, _store, _info, _ctxt) \
		__CPSW_ATTR_FULL(_name, _mode, _show, _store, _info, \
					(ARRAY_SIZE(_info)), _ctxt)

struct cpswx_mod_info {
	const char	*name;
	int		shift;
	int		bits;
};

struct cpswx_parse_result {
	int control;
	int port;
	u32 value;
};

static ssize_t cpsw_attr_info_show(const struct cpswx_mod_info *info,
				int info_size, u32 reg, char *buf)
{
	int i, len = 0;

	for (i = 0; i < info_size; i++, info++) {
		len += snprintf(buf + len, PAGE_SIZE - len,
			"%s=%d\n", info->name,
			(int)cpsw_mod_info_field_val(reg, info));
	}

	return len;
}

static ssize_t cpsw_attr_parse_set_command(struct cpswx_priv *cpsw_dev,
			      struct cpswx_attribute *attr,
			      const char *buf, size_t count,
				struct cpswx_parse_result *res)
{
	char ctrl_str[33], tmp_str[9];
	int port = -1, value, len, control;
	unsigned long end;
	const struct cpswx_mod_info *info = attr->info;

	len = strcspn(buf, ".=");
	if (len >= 32)
		return -ENOMEM;

	strncpy(ctrl_str, buf, len);
	ctrl_str[len] = '\0';
	buf += len;

	if (*buf == '.') {
		++buf;
		len = strcspn(buf, "=");
		if (len >= 8)
			return -ENOMEM;
		strncpy(tmp_str, buf, len);
		tmp_str[len] = '\0';
		if (kstrtoul(tmp_str, 0, &end))
			return -EINVAL;
		port = (int)end;
		buf += len;
	}

	if (*buf != '=')
		return -EINVAL;

	if (kstrtoul(buf + 1, 0, &end))
		return -EINVAL;

	value = (int)end;

	for (control = 0; control < attr->info_size; control++)
		if (strcmp(ctrl_str, info[control].name) == 0)
			break;

	if (control >= attr->info_size)
		return -ENOENT;

	res->control = control;
	res->port = port;
	res->value = value;

	dev_info(cpsw_dev->dev, "parsed command %s.%d=%d\n",
		attr->info[control].name, port, value);

	return 0;
}

static inline void cpsw_info_set_reg_field(void __iomem *r,
		const struct cpswx_mod_info *info, int val)
{
	u32 rv;

	rv = __raw_readl(r);
	rv = ((rv & ~BITMASK(info->bits, info->shift)) | (val << info->shift));
	__raw_writel(rv, r);
}

static ssize_t cpsw_version_show(struct cpswx_priv *cpsw_dev,
		     struct cpswx_attribute *attr,
		     char *buf)
{
	u32 reg;

	reg = __raw_readl(&cpsw_dev->regs->id_ver);

	return snprintf(buf, PAGE_SIZE,
		"cpsw version %d.%d (%d) SGMII identification value 0x%x\n",
		 CPSW_MAJOR_VERSION(reg), CPSW_MINOR_VERSION(reg),
		 CPSW_RTL_VERSION(reg), CPSW_IDENT(reg));
}

static struct cpswx_attribute cpsw_version_attribute =
	__ATTR(version, S_IRUGO, cpsw_version_show, NULL);

static const struct cpswx_mod_info cpsw_controls[] = {
	{
		.name		= "fifo_loopback",
		.shift		= 0,
		.bits		= 1,
	},
	{
		.name		= "vlan_aware",
		.shift		= 1,
		.bits		= 1,
	},
	{
		.name		= "p0_enable",
		.shift		= 2,
		.bits		= 1,
	},
	{
		.name		= "p0_pass_pri_tagged",
		.shift		= 3,
		.bits		= 1,
	},
	{
		.name		= "p1_pass_pri_tagged",
		.shift		= 4,
		.bits		= 1,
	},
	{
		.name		= "p2_pass_pri_tagged",
		.shift		= 5,
		.bits		= 1,
	},
	{
		.name		= "p0_tx_crc_type",
		.shift		= 12,
		.bits		= 1,
	},
};

static ssize_t cpsw_control_show(struct cpswx_priv *cpsw_dev,
		     struct cpswx_attribute *attr,
		     char *buf)
{
	u32 reg;

	reg = __raw_readl(&cpsw_dev->regs->control);
	return cpsw_attr_info_show(attr->info, attr->info_size, reg, buf);
}

static ssize_t cpsw_control_store(struct cpswx_priv *cpsw_dev,
			      struct cpswx_attribute *attr,
			      const char *buf, size_t count)
{
	const struct cpswx_mod_info *i;
	struct cpswx_parse_result res;
	void __iomem *r = NULL;
	int ret;


	ret = cpsw_attr_parse_set_command(cpsw_dev, attr, buf, count, &res);
	if (ret)
		return ret;

	i = &(attr->info[res.control]);
	r = &cpsw_dev->regs->control;

	cpsw_info_set_reg_field(r, i, res.value);
	return count;
}

static struct cpswx_attribute cpsw_control_attribute =
	__CPSW_ATTR(control, S_IRUGO | S_IWUSR,
		cpsw_control_show, cpsw_control_store, cpsw_controls);

static const struct cpswx_mod_info cpsw_ptypes[] = {
	{
		.name		= "escalate_pri_load_val",
		.shift		= 0,
		.bits		= 5,
	},
	{
		.name		= "port0_pri_type_escalate",
		.shift		= 8,
		.bits		= 1,
	},
	{
		.name		= "port1_pri_type_escalate",
		.shift		= 9,
		.bits		= 1,
	},
	{
		.name		= "port2_pri_type_escalate",
		.shift		= 10,
		.bits		= 1,
	},
};

static ssize_t cpsw_pri_type_show(struct cpswx_priv *cpsw_dev,
		     struct cpswx_attribute *attr,
		     char *buf)
{
	u32 reg;

	reg = __raw_readl(&cpsw_dev->regs->ptype);

	return cpsw_attr_info_show(attr->info, attr->info_size, reg, buf);
}

static ssize_t cpsw_pri_type_store(struct cpswx_priv *cpsw_dev,
			      struct cpswx_attribute *attr,
			      const char *buf, size_t count)
{
	const struct cpswx_mod_info *i;
	struct cpswx_parse_result res;
	void __iomem *r = NULL;
	int ret;


	ret = cpsw_attr_parse_set_command(cpsw_dev, attr, buf, count, &res);
	if (ret)
		return ret;

	i = &(attr->info[res.control]);
	r = &cpsw_dev->regs->ptype;

	cpsw_info_set_reg_field(r, i, res.value);
	return count;
}

static struct cpswx_attribute cpsw_pri_type_attribute =
	__CPSW_ATTR(priority_type, S_IRUGO | S_IWUSR,
			cpsw_pri_type_show,
			cpsw_pri_type_store,
			cpsw_ptypes);

static const struct cpswx_mod_info cpsw_flow_controls[] = {
	{
		.name		= "port0_flow_control_en",
		.shift		= 0,
		.bits		= 1,
	},
	{
		.name		= "port1_flow_control_en",
		.shift		= 1,
		.bits		= 1,
	},
	{
		.name		= "port2_flow_control_en",
		.shift		= 2,
		.bits		= 1,
	},
};

static ssize_t cpsw_flow_control_show(struct cpswx_priv *cpsw_dev,
		     struct cpswx_attribute *attr, char *buf)
{
	u32 reg;

	reg = __raw_readl(&cpsw_dev->regs->flow_control);

	return cpsw_attr_info_show(attr->info, attr->info_size, reg, buf);
}

static ssize_t cpsw_flow_control_store(struct cpswx_priv *cpsw_dev,
			      struct cpswx_attribute *attr,
			      const char *buf, size_t count)
{
	const struct cpswx_mod_info *i;
	struct cpswx_parse_result res;
	void __iomem *r = NULL;
	int ret;


	ret = cpsw_attr_parse_set_command(cpsw_dev, attr, buf, count, &res);
	if (ret)
		return ret;

	i = &(attr->info[res.control]);
	r = &cpsw_dev->regs->flow_control;

	cpsw_info_set_reg_field(r, i, res.value);
	return count;
}

static struct cpswx_attribute cpsw_flow_control_attribute =
	__CPSW_ATTR(flow_control, S_IRUGO | S_IWUSR,
		cpsw_flow_control_show,
		cpsw_flow_control_store,
		cpsw_flow_controls);

static const struct cpswx_mod_info cpsw_port_tx_pri_maps[] = {
	{
		.name		= "port_tx_pri_0",
		.shift		= 0,
		.bits		= 3,
	},
	{
		.name		= "port_tx_pri_1",
		.shift		= 4,
		.bits		= 3,
	},
	{
		.name		= "port_tx_pri_2",
		.shift		= 8,
		.bits		= 3,
	},
	{
		.name		= "port_tx_pri_3",
		.shift		= 12,
		.bits		= 3,
	},
	{
		.name		= "port_tx_pri_4",
		.shift		= 16,
		.bits		= 3,
	},
	{
		.name		= "port_tx_pri_5",
		.shift		= 20,
		.bits		= 3,
	},
	{
		.name		= "port_tx_pri_6",
		.shift		= 24,
		.bits		= 3,
	},
	{
		.name		= "port_tx_pri_7",
		.shift		= 28,
		.bits		= 3,
	},
};

static ssize_t cpsw_port_tx_pri_map_show(struct cpswx_priv *cpsw_dev,
		     struct cpswx_attribute *attr,
		     char *buf)
{
	int idx, len = 0, total_len = 0, port;
	struct cpswx_intf *cpsw_intf;
	struct cpswx_slave *slave;
	u32 reg;

	port = (int)(attr->context);

	/* Host port */
	if (port == cpsw_dev->host_port) {
		reg = __raw_readl(&cpsw_dev->host_port_regs->tx_pri_map);
		len = cpsw_attr_info_show(attr->info, attr->info_size,
					reg, buf);
		return len;
	}

	for_each_intf(cpsw_intf, cpsw_dev) {
		if (cpsw_intf->multi_if) {
			slave = cpsw_intf->slaves;
			if (slave->port_num != port)
				continue;
			reg = __raw_readl(&slave->regs->tx_pri_map);
			len = cpsw_attr_info_show(attr->info, attr->info_size,
						reg, buf+total_len);
			total_len += len;
		} else {
			for (idx = 0; idx < cpsw_intf->num_slaves; idx++) {
				slave = cpsw_intf->slaves + idx;
				if (slave->port_num != port)
					continue;
				reg = __raw_readl(&slave->regs->tx_pri_map);
				len = cpsw_attr_info_show(attr->info,
					attr->info_size, reg, buf+total_len);
				total_len += len;
			}
		}
	}
	return total_len;
}

static ssize_t cpsw_port_tx_pri_map_store(struct cpswx_priv *cpsw_dev,
			      struct cpswx_attribute *attr,
			      const char *buf, size_t count)
{
	const struct cpswx_mod_info *i;
	struct cpswx_parse_result res;
	struct cpswx_intf *cpsw_intf;
	struct cpswx_slave *slave;
	void __iomem *r = NULL;
	int ret, idx, port;

	port = (int)(attr->context);

	ret = cpsw_attr_parse_set_command(cpsw_dev, attr, buf, count, &res);
	if (ret)
		return ret;

	i = &(attr->info[res.control]);

	/* Host port */
	if (port == cpsw_dev->host_port) {
		r = &cpsw_dev->host_port_regs->tx_pri_map;
		goto set;
	}

	/* Slave port */
	for_each_intf(cpsw_intf, cpsw_dev) {
		if (cpsw_intf->multi_if) {
			slave = cpsw_intf->slaves;
			if (slave->port_num == port) {
				r = &slave->regs->tx_pri_map;
				goto set;
			}
		} else
			for (idx = 0; idx < cpsw_intf->num_slaves; idx++) {
				slave = cpsw_intf->slaves + idx;
				if (slave->port_num == port) {
					r = &slave->regs->tx_pri_map;
					goto set;
				}
			}
	}

	if (!r)
		return  -ENOENT;

set:
	cpsw_info_set_reg_field(r, i, res.value);
	return count;
}

static struct cpswx_attribute cpsw_tx_pri_0_attribute =
	__CPSW_CTXT_ATTR(0, S_IRUGO | S_IWUSR,
			cpsw_port_tx_pri_map_show,
			cpsw_port_tx_pri_map_store,
			cpsw_port_tx_pri_maps, (void *)0);

static struct cpswx_attribute cpsw_tx_pri_1_attribute =
	__CPSW_CTXT_ATTR(1, S_IRUGO | S_IWUSR,
			cpsw_port_tx_pri_map_show,
			cpsw_port_tx_pri_map_store,
			cpsw_port_tx_pri_maps, (void *)1);

static struct cpswx_attribute cpsw_tx_pri_2_attribute =
	__CPSW_CTXT_ATTR(2, S_IRUGO | S_IWUSR,
			cpsw_port_tx_pri_map_show,
			cpsw_port_tx_pri_map_store,
			cpsw_port_tx_pri_maps, (void *)2);

static struct attribute *cpsw_tx_pri_default_attrs[] = {
	&cpsw_tx_pri_0_attribute.attr,
	&cpsw_tx_pri_1_attribute.attr,
	&cpsw_tx_pri_2_attribute.attr,
	NULL
};

static ssize_t cpsw_tx_pri_attr_show(struct kobject *kobj,
			struct attribute *attr, char *buf)
{
	struct cpswx_attribute *attribute = to_cpswx_attr(attr);
	struct cpswx_priv *cpsw_dev = tx_pri_to_cpswx_dev(kobj);

	if (!attribute->show)
		return -EIO;

	return attribute->show(cpsw_dev, attribute, buf);
}

static ssize_t cpsw_tx_pri_attr_store(struct kobject *kobj,
			struct attribute *attr, const char *buf, size_t count)
{
	struct cpswx_attribute *attribute = to_cpswx_attr(attr);
	struct cpswx_priv *cpsw_dev = tx_pri_to_cpswx_dev(kobj);

	if (!attribute->store)
		return -EIO;

	return attribute->store(cpsw_dev, attribute, buf, count);
}

static const struct sysfs_ops cpsw_tx_pri_sysfs_ops = {
	.show = cpsw_tx_pri_attr_show,
	.store = cpsw_tx_pri_attr_store,
};

static struct kobj_type cpsw_tx_pri_ktype = {
	.sysfs_ops = &cpsw_tx_pri_sysfs_ops,
	.default_attrs = cpsw_tx_pri_default_attrs,
};

static const struct cpswx_mod_info cpsw_port_vlans[] = {
	{
		.name		= "port_vlan_id",
		.shift		= 0,
		.bits		= 12,
	},
	{
		.name		= "port_cfi",
		.shift		= 12,
		.bits		= 1,
	},
	{
		.name		= "port_vlan_pri",
		.shift		= 13,
		.bits		= 3,
	},
};

static ssize_t cpsw_port_vlan_show(struct cpswx_priv *cpsw_dev,
		     struct cpswx_attribute *attr,
		     char *buf)
{
	int idx, len = 0, total_len = 0, port;
	struct cpswx_intf *cpsw_intf;
	struct cpswx_slave *slave;
	u32 reg;

	port = (int)(attr->context);

	if (port == cpsw_dev->host_port) {
		/* Host port */
		reg = __raw_readl(&cpsw_dev->host_port_regs->port_vlan);
		len = cpsw_attr_info_show(attr->info, attr->info_size,
					reg, buf);
		return len;
	}

	/* Slave ports */
	for_each_intf(cpsw_intf, cpsw_dev) {
		if (cpsw_intf->multi_if) {
			slave = cpsw_intf->slaves;
			if (slave->port_num != port)
				continue;
			reg = __raw_readl(&slave->regs->port_vlan);
			len = cpsw_attr_info_show(attr->info, attr->info_size,
					reg, buf+total_len);
			total_len += len;
		} else {
			for (idx = 0; idx < cpsw_intf->num_slaves; idx++) {
				slave = cpsw_intf->slaves + idx;
				if (slave->port_num != port)
					continue;
				reg = __raw_readl(&slave->regs->port_vlan);
				len = cpsw_attr_info_show(attr->info,
					attr->info_size, reg, buf+total_len);
				total_len += len;
			}
		}
	}
	return total_len;
}

static ssize_t cpsw_port_vlan_store(struct cpswx_priv *cpsw_dev,
			      struct cpswx_attribute *attr,
			      const char *buf, size_t count)
{
	const struct cpswx_mod_info *i;
	struct cpswx_parse_result res;
	struct cpswx_intf *cpsw_intf;
	struct cpswx_slave *slave;
	void __iomem *r = NULL;
	int ret, idx, port;

	port = (int)(attr->context);

	ret = cpsw_attr_parse_set_command(cpsw_dev, attr, buf, count, &res);
	if (ret)
		return ret;

	i = &(attr->info[res.control]);

	/* Host port */
	if (port == cpsw_dev->host_port) {
		r = &cpsw_dev->host_port_regs->port_vlan;
		goto set;
	}

	/* Slave port */
	for_each_intf(cpsw_intf, cpsw_dev) {
		if (cpsw_intf->multi_if) {
			slave = cpsw_intf->slaves;
			if (slave->port_num == port) {
				r = &slave->regs->port_vlan;
				goto set;
			}
		} else
			for (idx = 0; idx < cpsw_intf->num_slaves; idx++) {
				slave = cpsw_intf->slaves + idx;
				if (slave->port_num == port) {
					r = &slave->regs->port_vlan;
					goto set;
				}
			}
	}

	if (!r)
		return  -ENOENT;

set:
	cpsw_info_set_reg_field(r, i, res.value);
	return count;
}

static struct cpswx_attribute cpsw_pvlan_0_attribute =
	__CPSW_CTXT_ATTR(0, S_IRUGO | S_IWUSR,
			cpsw_port_vlan_show,
			cpsw_port_vlan_store,
			cpsw_port_vlans, (void *)0);

static struct cpswx_attribute cpsw_pvlan_1_attribute =
	__CPSW_CTXT_ATTR(1, S_IRUGO | S_IWUSR,
			cpsw_port_vlan_show,
			cpsw_port_vlan_store,
			cpsw_port_vlans, (void *)1);

static struct cpswx_attribute cpsw_pvlan_2_attribute =
	__CPSW_CTXT_ATTR(2, S_IRUGO | S_IWUSR,
			cpsw_port_vlan_show,
			cpsw_port_vlan_store,
			cpsw_port_vlans, (void *)2);

static struct attribute *cpsw_pvlan_default_attrs[] = {
	&cpsw_pvlan_0_attribute.attr,
	&cpsw_pvlan_1_attribute.attr,
	&cpsw_pvlan_2_attribute.attr,
	NULL
};

static ssize_t cpsw_pvlan_attr_show(struct kobject *kobj,
			struct attribute *attr, char *buf)
{
	struct cpswx_attribute *attribute = to_cpswx_attr(attr);
	struct cpswx_priv *cpsw_dev = pvlan_to_cpswx_dev(kobj);

	if (!attribute->show)
		return -EIO;

	return attribute->show(cpsw_dev, attribute, buf);
}

static ssize_t cpsw_pvlan_attr_store(struct kobject *kobj,
			struct attribute *attr, const char *buf, size_t count)
{
	struct cpswx_attribute *attribute = to_cpswx_attr(attr);
	struct cpswx_priv *cpsw_dev = pvlan_to_cpswx_dev(kobj);

	if (!attribute->store)
		return -EIO;

	return attribute->store(cpsw_dev, attribute, buf, count);
}

static const struct sysfs_ops cpsw_pvlan_sysfs_ops = {
	.show = cpsw_pvlan_attr_show,
	.store = cpsw_pvlan_attr_store,
};

static struct kobj_type cpsw_pvlan_ktype = {
	.sysfs_ops = &cpsw_pvlan_sysfs_ops,
	.default_attrs = cpsw_pvlan_default_attrs,
};

static void cpsw_reset_mod_stats(struct cpswx_priv *cpsw_dev, int stat_mod)
{
	struct cpswx_host_hw_stats __iomem *cpsw_stats0;
	struct cpswx_hw_stats __iomem *cpsw_statsa;
	struct cpswx_hw_stats __iomem *cpsw_statsb;
	void __iomem *base;
	u32  __iomem *p;
	int i;

	cpsw_stats0 = cpsw_dev->host_hw_stats_regs;
	cpsw_statsa = cpsw_dev->hw_stats_regs[0];
	cpsw_statsb = cpsw_dev->hw_stats_regs[1];

	switch (stat_mod) {
	case CPSW_STATS0_MODULE:
		base = cpsw_stats0;
		break;
	case CPSW_STATS1_MODULE:
		base = cpsw_statsa;
		break;
	case CPSW_STATS2_MODULE:
		base  = cpsw_statsb;
		break;
	default:
		dev_err(cpsw_dev->dev, "Unknown stat module %d\n", stat_mod);
		return;
	}

	for (i = 0; i < ETHTOOL_STATS_NUM; i++) {
		if (et_stats[i].type == stat_mod) {
			cpsw_dev->hw_stats[i] = 0;
			p = base + et_stats[i].offset;
			*p = 0xffffffff;
		}
	}
	return;
}

static ssize_t cpsw_stats_mod_store(struct cpswx_priv *cpsw_dev,
			      struct cpswx_attribute *attr,
			      const char *buf, size_t count)
{
	unsigned long end;
	int stat_mod;

	if (kstrtoul(buf, 0, &end) != 0 || (end != 0))
		return -EINVAL;

	stat_mod = (int)(attr->context);
	spin_lock_bh(&cpsw_dev->hw_stats_lock);
	cpsw_reset_mod_stats(cpsw_dev, stat_mod);
	spin_unlock_bh(&cpsw_dev->hw_stats_lock);
	return count;
}

static struct cpswx_attribute cpsw_stats_0_attribute =
	__CPSW_ATTR_FULL(0, S_IWUSR, NULL, cpsw_stats_mod_store,
			NULL, 0, (void *)CPSW_STATS0_MODULE);

static struct cpswx_attribute cpsw_stats_1_attribute =
	__CPSW_ATTR_FULL(1, S_IWUSR, NULL, cpsw_stats_mod_store,
			NULL, 0, (void *)CPSW_STATS1_MODULE);

static struct cpswx_attribute cpsw_stats_2_attribute =
	__CPSW_ATTR_FULL(2, S_IWUSR, NULL, cpsw_stats_mod_store,
			NULL, 0, (void *)CPSW_STATS2_MODULE);

static struct attribute *cpsw_stats_default_attrs[] = {
	&cpsw_stats_0_attribute.attr,
	&cpsw_stats_1_attribute.attr,
	&cpsw_stats_2_attribute.attr,
	NULL
};

static ssize_t cpsw_stats_attr_store(struct kobject *kobj,
			struct attribute *attr, const char *buf, size_t count)
{
	struct cpswx_attribute *attribute = to_cpswx_attr(attr);
	struct cpswx_priv *cpsw_dev = stats_to_cpswx_dev(kobj);

	if (!attribute->store)
		return -EIO;

	return attribute->store(cpsw_dev, attribute, buf, count);
}

static const struct sysfs_ops cpsw_stats_sysfs_ops = {
	.store = cpsw_stats_attr_store,
};

static struct kobj_type cpsw_stats_ktype = {
	.sysfs_ops = &cpsw_stats_sysfs_ops,
	.default_attrs = cpsw_stats_default_attrs,
};
static struct attribute *cpsw_default_attrs[] = {
	&cpsw_version_attribute.attr,
	&cpsw_control_attribute.attr,
	&cpsw_pri_type_attribute.attr,
	&cpsw_flow_control_attribute.attr,
	NULL
};

static ssize_t cpsw_attr_show(struct kobject *kobj, struct attribute *attr,
				  char *buf)
{
	struct cpswx_attribute *attribute = to_cpswx_attr(attr);
	struct cpswx_priv *cpsw_dev = to_cpswx_dev(kobj);

	if (!attribute->show)
		return -EIO;

	return attribute->show(cpsw_dev, attribute, buf);
}

static ssize_t cpsw_attr_store(struct kobject *kobj, struct attribute *attr,
				   const char *buf, size_t count)
{
	struct cpswx_attribute *attribute = to_cpswx_attr(attr);
	struct cpswx_priv *cpsw_dev = to_cpswx_dev(kobj);

	if (!attribute->store)
		return -EIO;

	return attribute->store(cpsw_dev, attribute, buf, count);
}

static const struct sysfs_ops cpsw_sysfs_ops = {
	.show = cpsw_attr_show,
	.store = cpsw_attr_store,
};

static struct kobj_type cpsw_ktype = {
	.sysfs_ops = &cpsw_sysfs_ops,
	.default_attrs = cpsw_default_attrs,
};

static void keystone_get_drvinfo(struct net_device *ndev,
			     struct ethtool_drvinfo *info)
{
	strncpy(info->driver, NETCP_DRIVER_NAME, sizeof(info->driver));
	strncpy(info->version, NETCP_DRIVER_VERSION, sizeof(info->version));
}

static u32 keystone_get_msglevel(struct net_device *ndev)
{
	struct netcp_priv *netcp = netdev_priv(ndev);
	return netcp->msg_enable;
}

static void keystone_set_msglevel(struct net_device *ndev, u32 value)
{
	struct netcp_priv *netcp = netdev_priv(ndev);
	netcp->msg_enable = value;
}

static void keystone_get_stat_strings(struct net_device *ndev,
				   uint32_t stringset, uint8_t *data)
{
	int i;

	switch (stringset) {
	case ETH_SS_STATS:
		for (i = 0; i < ETHTOOL_STATS_NUM; i++) {
			memcpy(data, et_stats[i].desc, ETH_GSTRING_LEN);
			data += ETH_GSTRING_LEN;
		}
		break;
	case ETH_SS_TEST:
		break;
	}
}

static int keystone_get_sset_count(struct net_device *ndev, int stringset)
{
	switch (stringset) {
	case ETH_SS_TEST:
		return 0;
	case ETH_SS_STATS:
		return ETHTOOL_STATS_NUM;
	default:
		return -EINVAL;
	}
}

static void cpswx_update_stats(struct cpswx_priv *cpsw_dev, uint64_t *data)
{
	struct cpswx_host_hw_stats __iomem *cpsw_stats0;
	struct cpswx_hw_stats __iomem *cpsw_statsa;
	struct cpswx_hw_stats __iomem *cpsw_statsb;
	void __iomem *base = NULL;
	u32  __iomem *p;
	u32 tmp = 0;
	int i;

	cpsw_stats0 = cpsw_dev->host_hw_stats_regs;
	cpsw_statsa = cpsw_dev->hw_stats_regs[0];
	cpsw_statsb = cpsw_dev->hw_stats_regs[1];

	for (i = 0; i < ETHTOOL_STATS_NUM; i++) {
		switch (et_stats[i].type) {
		case CPSW_STATS0_MODULE:
			base = cpsw_stats0;
			break;
		case CPSW_STATS1_MODULE:
			base = cpsw_statsa;
			break;
		case CPSW_STATS2_MODULE:
			base = cpsw_statsb;
			break;
		default:
			dev_err(cpsw_dev->dev, "Unknown stat module %d\n",
				et_stats[i].type);
			return;
		}

		p = base + et_stats[i].offset;
		tmp = *p;
		cpsw_dev->hw_stats[i] = cpsw_dev->hw_stats[i] + tmp;
		if (data)
			data[i] = cpsw_dev->hw_stats[i];
		*p = tmp;
	}

	return;
}

static void keystone_get_ethtool_stats(struct net_device *ndev,
				       struct ethtool_stats *stats,
				       uint64_t *data)
{
	struct netcp_priv *netcp = netdev_priv(ndev);
	struct netcp_device *netcp_device = netcp->netcp_device;
	struct cpswx_priv *priv;

	/* find the instance of the module registered to the netcp_device */
	priv = (struct cpswx_priv *)netcp_device_find_module(netcp_device,
							CPSW_MODULE_NAME);
	if (priv) {
		spin_lock_bh(&priv->hw_stats_lock);
		cpswx_update_stats(priv, data);
		spin_unlock_bh(&priv->hw_stats_lock);
	}

	return;
}

static int keystone_get_settings(struct net_device *ndev,
			      struct ethtool_cmd *cmd)
{
	struct phy_device *phy = ndev->phydev;
	struct cpswx_slave *slave;
	int ret;

	if (!phy)
		return -EINVAL;

	slave = (struct cpswx_slave *)phy->context;
	if (!slave)
		return -EINVAL;

	ret = phy_ethtool_gset(phy, cmd);
	if (!ret)
		cmd->port = slave->phy_port_t;

	return ret;
}

static int keystone_set_settings(struct net_device *ndev,
				struct ethtool_cmd *cmd)
{
	struct phy_device *phy = ndev->phydev;
	struct cpswx_slave *slave;
	u32 features = cmd->advertising & cmd->supported;

	if (!phy)
		return -EINVAL;

	slave = (struct cpswx_slave *)phy->context;
	if (!slave)
		return -EINVAL;

	if (cmd->port != slave->phy_port_t) {
		if ((cmd->port == PORT_TP) && !(features & ADVERTISED_TP))
			return -EINVAL;

		if ((cmd->port == PORT_AUI) && !(features & ADVERTISED_AUI))
			return -EINVAL;

		if ((cmd->port == PORT_BNC) && !(features & ADVERTISED_BNC))
			return -EINVAL;

		if ((cmd->port == PORT_MII) && !(features & ADVERTISED_MII))
			return -EINVAL;

		if ((cmd->port == PORT_FIBRE) && !(features & ADVERTISED_FIBRE))
			return -EINVAL;
	}

	slave->phy_port_t = cmd->port;

	return phy_ethtool_sset(phy, cmd);
}

static const struct ethtool_ops keystone_ethtool_ops = {
	.get_drvinfo		= keystone_get_drvinfo,
	.get_link		= ethtool_op_get_link,
	.get_msglevel		= keystone_get_msglevel,
	.set_msglevel		= keystone_set_msglevel,
	.get_strings		= keystone_get_stat_strings,
	.get_sset_count		= keystone_get_sset_count,
	.get_ethtool_stats	= keystone_get_ethtool_stats,
	.get_settings		= keystone_get_settings,
	.set_settings		= keystone_set_settings,
};

#define mac_hi(mac)	(((mac)[0] << 0) | ((mac)[1] << 8) |	\
			 ((mac)[2] << 16) | ((mac)[3] << 24))
#define mac_lo(mac)	(((mac)[4] << 0) | ((mac)[5] << 8))

static void cpsw_set_slave_mac(struct cpswx_slave *slave,
			       struct cpswx_intf *cpsw_intf)
{
	struct net_device *ndev = cpsw_intf->ndev;

	__raw_writel(mac_hi(ndev->dev_addr), &slave->regs->sa_hi);
	__raw_writel(mac_lo(ndev->dev_addr), &slave->regs->sa_lo);
}

static inline int cpsw_get_slave_port(struct cpswx_priv *priv, u32 slave_num)
{
	if (priv->host_port == 0)
		return slave_num + 1;
	else
		return slave_num;
}

static void _cpsw_adjust_link(struct cpswx_slave *slave, bool *link)
{
	struct phy_device *phy = slave->phy;
	u32 mac_control = 0;
	u32 slave_port;

	if (!phy)
		return;

	if (!slave->ale)
		return;

	slave_port = slave->port_num;

	if (phy->link) {
		mac_control = slave->mac_control;
		mac_control |= MACSL_DEFAULT_CONFIG(slave);
		/* enable forwarding */
		cpsw_ale_control_set(slave->ale, slave_port,
				     ALE_PORT_STATE, ALE_PORT_STATE_FORWARD);

		if (phy->duplex)
			mac_control |= BIT(0);	/* FULLDUPLEXEN	*/
		else
			mac_control &= ~0x1;

		*link = true;
	} else {
		mac_control = 0;
		/* disable forwarding */
		cpsw_ale_control_set(slave->ale, slave_port,
				     ALE_PORT_STATE, ALE_PORT_STATE_DISABLE);
	}

	if (mac_control != slave->mac_control) {
		phy_print_status(phy);
		__raw_writel(mac_control, &slave->sliver->mac_control);
	}

	slave->mac_control = mac_control;
}

static void cpsw_adjust_link(struct net_device *n_dev, void *context)
{
	struct cpswx_slave *slave = (struct cpswx_slave *)context;
	struct netcp_priv *netcp = netdev_priv(n_dev);
	bool link = false;

	_cpsw_adjust_link(slave, &link);

	if (link)
		netcp->phy_link_state_mask |= BIT(slave->slave_num);
	else
		netcp->phy_link_state_mask &= ~BIT(slave->slave_num);
}

/*
 * Reset the the mac sliver
 * Soft reset is set and polled until clear, or until a timeout occurs
 */
static int cpsw_port_reset(struct cpswx_slave *slave)
{
	u32 i, v;

	/* Set the soft reset bit */
	__raw_writel(SOFT_RESET,
		     &slave->sliver->soft_reset);

	/* Wait for the bit to clear */
	for (i = 0; i < DEVICE_EMACSL_RESET_POLL_COUNT; i++) {
		v = __raw_readl(&slave->sliver->soft_reset);
		if ((v & SOFT_RESET_MASK) !=
		    SOFT_RESET)
			return 0;
	}

	/* Timeout on the reset */
	return GMACSL_RET_WARN_RESET_INCOMPLETE;
}

/*
 * Configure the mac sliver
 */
static void cpsw_port_config(struct cpswx_slave *slave, int max_rx_len)
{
	if (max_rx_len > MAX_SIZE_STREAM_BUFFER)
		max_rx_len = MAX_SIZE_STREAM_BUFFER;

	slave->mac_control = MACSL_DEFAULT_CONFIG(slave);

	__raw_writel(max_rx_len, &slave->sliver->rx_maxlen);

	__iowmb();
	__raw_writel(slave->mac_control, &slave->sliver->mac_control);
}

static void cpsw_slave_stop(struct cpswx_slave *slave, struct cpswx_priv *priv)
{
	cpsw_port_reset(slave);

	if (!slave->phy)
		return;

	phy_stop(slave->phy);
	phy_disconnect(slave->phy);
	slave->phy = NULL;
}

static void cpsw_slave_link(struct cpswx_slave *slave,
			    struct cpswx_intf *cpsw_intf)
{
	struct netcp_priv *netcp = netdev_priv(cpsw_intf->ndev);
	int sn = slave->slave_num;

	if ((slave->link_interface == SGMII_LINK_MAC_PHY) ||
		(slave->link_interface == XGMII_LINK_MAC_PHY)) {
		/* check only the bit in phy_link_state_mask
		 * that corresponds to the slave
		 */
		if (!(netcp->phy_link_state_mask & BIT(sn)))
			cpsw_intf->link_state &= ~BIT(sn);
	} else if (slave->link_interface == XGMII_LINK_MAC_MAC_FORCED)
		cpsw_intf->link_state |= BIT(slave->slave_num);
}

static void cpsw_slave_open(struct cpswx_slave *slave,
			    struct cpswx_intf *cpsw_intf)
{
	struct cpswx_priv *priv = cpsw_intf->cpsw_priv;
	char name[32];		/* FIXME: Unused variable */
	u32 slave_port;
	int has_phy = 0;
	phy_interface_t phy_mode;

	snprintf(name, sizeof(name), "slave-%d", slave->slave_num);

	if (!SLAVE_LINK_IS_XGMII(slave)) {
		keystone_sgmii_reset(priv->sgmii_port_regs, slave->slave_num);

		keystone_sgmii_config(priv->sgmii_port_regs, slave->slave_num,
				slave->link_interface);
	} else
		keystone_pcsr_config(priv->pcsr_port_regs, slave->slave_num,
				slave->link_interface);

	cpsw_port_reset(slave);

	cpsw_port_config(slave, priv->rx_packet_max);

	cpsw_set_slave_mac(slave, cpsw_intf);

	slave_port = cpsw_get_slave_port(priv, slave->slave_num);

	slave->port_num = slave_port;
	slave->ale = priv->ale;

	/* enable forwarding */
	cpsw_ale_control_set(priv->ale, slave_port,
			     ALE_PORT_STATE, ALE_PORT_STATE_FORWARD);

	cpsw_ale_add_mcast(priv->ale, cpsw_intf->ndev->broadcast,
			   1 << slave_port, 0, 0, ALE_MCAST_FWD_2);

	if (slave->link_interface == SGMII_LINK_MAC_PHY) {
		has_phy = 1;
		phy_mode = PHY_INTERFACE_MODE_SGMII;
		slave->phy_port_t = PORT_MII;
	} else if (slave->link_interface == XGMII_LINK_MAC_PHY) {
		has_phy = 1;
		/* +++FIXME: PHY_INTERFACE_MODE_XGMII ?? */
		phy_mode = PHY_INTERFACE_MODE_NA;
		slave->phy_port_t = PORT_FIBRE;
	}

	if (has_phy) {
		slave->phy = of_phy_connect(cpsw_intf->ndev,
					    cpsw_intf->phy_node,
					    &cpsw_adjust_link, 0,
					    phy_mode,
					    slave);
		if (IS_ERR_OR_NULL(slave->phy)) {
			dev_err(priv->dev, "phy not found on slave %d\n",
				slave->slave_num);
			slave->phy = NULL;
		} else {
			dev_info(priv->dev, "phy found: id is: %s, drv: %s\n",
				 dev_name(&slave->phy->dev),
				 (slave->phy->drv ?
				   (slave->phy->drv->name ?
					slave->phy->drv->name : "") : ""));
			cpsw_intf->ndev->phydev = slave->phy;
			phy_start(slave->phy);
		}
	}
}

static int cpsw_init_ale(struct cpswx_priv *cpsw_dev,
				struct cpswx_intf *cpsw_intf)
{
	struct cpsw_ale_params ale_params;

	memset(&ale_params, 0, sizeof(ale_params));

	ale_params.dev		= cpsw_dev->dev;
	ale_params.ale_regs	= (void *)((u32)cpsw_dev->ale_reg);
	ale_params.ale_ageout	= cpsw_dev->ale_ageout;
	ale_params.ale_entries	= cpsw_dev->ale_entries;
	ale_params.ale_ports	= cpsw_dev->ale_ports;

	cpsw_dev->ale = cpsw_ale_create(&ale_params);
	if (!cpsw_dev->ale) {
		dev_err(cpsw_dev->dev, "error initializing ale engine\n");
		return -ENODEV;
	}

	dev_info(cpsw_dev->dev, "Created a cpsw ale engine\n");
	
	cpsw_ale_start(cpsw_dev->ale);

	cpsw_ale_control_set(cpsw_dev->ale, 0, ALE_BYPASS,
			cpsw_dev->multi_if ? 1 : 0);

	cpsw_ale_control_set(cpsw_dev->ale, 0, ALE_NO_PORT_VLAN, 1);

	cpsw_ale_control_set(cpsw_dev->ale, cpsw_dev->host_port,
			     ALE_PORT_STATE, ALE_PORT_STATE_FORWARD);

	cpsw_ale_control_set(cpsw_dev->ale, 0,
			     ALE_PORT_UNKNOWN_VLAN_MEMBER,
			     CPSW_MASK_ALL_PORTS);

	cpsw_ale_control_set(cpsw_dev->ale, 0,
			     ALE_PORT_UNKNOWN_MCAST_FLOOD,
			     CPSW_MASK_PHYS_PORTS);

	cpsw_ale_control_set(cpsw_dev->ale, 0,
			     ALE_PORT_UNKNOWN_REG_MCAST_FLOOD,
			     CPSW_MASK_ALL_PORTS);

	cpsw_ale_control_set(cpsw_dev->ale, 0,
			     ALE_PORT_UNTAGGED_EGRESS,
			     CPSW_MASK_ALL_PORTS);

	return 0;
}

static void cpsw_init_host_port(struct cpswx_priv *priv,
				struct cpswx_intf *cpsw_intf)
{
	/* Host Tx Pri */
	__raw_writel(HOST_TX_PRI_MAP_DEFAULT,
		     &priv->host_port_regs->tx_pri_map);

	/* Max length register */
	__raw_writel(MAX_SIZE_STREAM_BUFFER,
		     &priv->host_port_regs->rx_maxlen);
}

static void cpsw_slave_init(struct cpswx_slave *slave, struct cpswx_priv *priv)
{
	void __iomem		*regs = priv->ss_regs;
	int			slave_num = slave->slave_num;

	slave->regs	= regs + priv->slave_reg_ofs + (0x30 * slave_num);
	slave->sliver	= regs + priv->sliver_reg_ofs + (0x40 * slave_num);
}

static void cpsw_add_mcast_addr(struct cpswx_intf *cpsw_intf, u8 *addr)
{
	struct cpswx_priv *cpsw_dev = cpsw_intf->cpsw_priv;

	cpsw_ale_add_mcast(cpsw_dev->ale, addr, CPSW_MASK_ALL_PORTS, 0, 0,
			   ALE_MCAST_FWD_2);
}

static void cpsw_add_ucast_addr(struct cpswx_intf *cpsw_intf, u8 *addr)
{
	struct cpswx_priv *cpsw_dev = cpsw_intf->cpsw_priv;

	cpsw_ale_add_ucast(cpsw_dev->ale, addr, cpsw_dev->host_port, 0, 0);
}

static void cpsw_del_mcast_addr(struct cpswx_intf *cpsw_intf, u8 *addr)
{
	struct cpswx_priv *cpsw_dev = cpsw_intf->cpsw_priv;

	cpsw_ale_del_mcast(cpsw_dev->ale, addr, 0, 0, 0);
}

static void cpsw_del_ucast_addr(struct cpswx_intf *cpsw_intf, u8 *addr)
{
	struct cpswx_priv *cpsw_dev = cpsw_intf->cpsw_priv;

	cpsw_ale_del_ucast(cpsw_dev->ale, addr, cpsw_dev->host_port, 0, 0);
}

static int cpswx_add_addr(void *intf_priv, struct netcp_addr *naddr)
{
	struct cpswx_intf *cpsw_intf = intf_priv;
	struct cpswx_priv *cpsw_dev = cpsw_intf->cpsw_priv;

	if (!cpsw_dev->opened)
		return -ENXIO;

	dev_dbg(cpsw_dev->dev, "xgess adding address %pM, type %d\n",
		naddr->addr, naddr->type);

	switch (naddr->type) {
	case ADDR_MCAST:
	case ADDR_BCAST:
		cpsw_add_mcast_addr(cpsw_intf, naddr->addr);
		break;
	case ADDR_UCAST:
	case ADDR_DEV:
		cpsw_add_ucast_addr(cpsw_intf, naddr->addr);
		break;
	case ADDR_ANY:
		/* nothing to do for promiscuous */
	default:
		break;
	}

	return 0;
}

static int cpswx_del_addr(void *intf_priv, struct netcp_addr *naddr)
{
	struct cpswx_intf *cpsw_intf = intf_priv;
	struct cpswx_priv *cpsw_dev = cpsw_intf->cpsw_priv;

	if (!cpsw_dev->opened)
		return -ENXIO;

	dev_dbg(cpsw_dev->dev, "xgess deleting address %pM, type %d\n",
		naddr->addr, naddr->type);

	switch (naddr->type) {
	case ADDR_MCAST:
	case ADDR_BCAST:
		cpsw_del_mcast_addr(cpsw_intf, naddr->addr);
		break;
	case ADDR_UCAST:
	case ADDR_DEV:
		cpsw_del_ucast_addr(cpsw_intf, naddr->addr);
		break;
	case ADDR_ANY:
		/* nothing to do for promiscuous */
	default:
		break;
	}

	return 0;
}

static int cpswx_ioctl(void *intf_priv, struct ifreq *req, int cmd)
{
	struct cpswx_intf *cpsw_intf = intf_priv;
	struct cpswx_slave *slave = cpsw_intf->slaves;
	struct phy_device *phy = slave->phy;
	int ret;

	if (!phy)
		return -EOPNOTSUPP;

	ret = phy_mii_ioctl(phy, req, cmd);
	if ((cmd == SIOCSHWTSTAMP) && (ret == -ERANGE))
		ret = -EOPNOTSUPP;

	return ret;
}

static void cpswx_timer(unsigned long arg)
{
	struct cpswx_intf *cpsw_intf = (struct cpswx_intf *)arg;
	struct cpswx_priv *cpsw_dev = cpsw_intf->cpsw_priv;

	/*
	 * if the slave's link_interface is not XGMII, link_state bit
	 * will not be set
	 */
	if (cpsw_dev->multi_if)
		cpsw_intf->link_state =
			keystone_sgmii_get_port_link(cpsw_dev->sgmii_port_regs,
						     cpsw_intf->slave_port);
	else
		cpsw_intf->link_state =
			keystone_sgmii_link_status(cpsw_dev->sgmii_port_regs,
						   cpsw_intf->num_slaves);

	/* if MAC-to-PHY, check phy link status also
	 * to conclude the intf link's status
	 */
	for_each_slave(cpsw_intf, cpsw_slave_link, cpsw_intf);

	/* Is this the right logic?
	 *  multi_if & MAC_PHY: phy state machine already reported carrier
	 *  multi_if & !MAC_PHY: report carrier
	 * !multi_if: any one slave up means intf is up, reporting carrier
	 *            here corrects what phy state machine (if it exists)
	 *            might have reported.
	 */
	if (!cpsw_dev->multi_if ||
	    (cpsw_dev->multi_if &&
	     (cpsw_intf->slaves->link_interface != SGMII_LINK_MAC_PHY) &&
	     (cpsw_intf->slaves->link_interface != XGMII_LINK_MAC_PHY))) {
		if (cpsw_intf->link_state)
			netif_carrier_on(cpsw_intf->ndev);
		else
			netif_carrier_off(cpsw_intf->ndev);
	}

	/* A timer runs as a BH, no need to block them */
	spin_lock(&cpsw_dev->hw_stats_lock);
	cpswx_update_stats(cpsw_dev, NULL);
	spin_unlock(&cpsw_dev->hw_stats_lock);

	cpsw_intf->timer.expires = jiffies + (HZ/10);
	add_timer(&cpsw_intf->timer);

	return;
}

static int cpsw_tx_hook(int order, void *data, struct netcp_packet *p_info)
{
	struct cpswx_intf *cpsw_intf = data;

	p_info->tx_pipe = &cpsw_intf->tx_pipe;
	return 0;
}

#define	CPSW_TXHOOK_ORDER	0

static int cpswx_open(void *intf_mod_priv, struct net_device *ndev)
{
	struct cpswx_intf *cpsw_intf = intf_mod_priv;
	struct cpswx_priv *cpsw_dev = cpsw_intf->cpsw_priv;
	struct netcp_priv *netcp = netdev_priv(ndev);
	u32 xgmii_mode = 0;
	int ret = 0;
	u32 reg, i;

	cpsw_dev->clk = clk_get(cpsw_dev->dev, "clk_xge");
	if (IS_ERR(cpsw_dev->clk)) {
		ret = PTR_ERR(cpsw_dev->clk);
		cpsw_dev->clk = NULL;
		dev_err(cpsw_dev->dev,
			"unable to get Keystone XGE clock: %d\n", ret);
		return ret;
	}

	ret = clk_prepare_enable(cpsw_dev->clk);
	if (ret)
		goto clk_fail;

	reg = __raw_readl(&cpsw_dev->regs->id_ver);

	dev_info(cpsw_dev->dev,
	 "initialize cpsw version %d.%d (%d) CPSW identification value 0x%x\n",
	 CPSW_MAJOR_VERSION(reg), CPSW_MINOR_VERSION(reg),
	 CPSW_RTL_VERSION(reg), CPSW_IDENT(reg));

	ret = netcp_txpipe_open(&cpsw_intf->tx_pipe);
	if (ret)
		goto txpipe_fail;

	dev_dbg(cpsw_dev->dev, "opened TX channel %s: %p with psflags %d\n",
		cpsw_intf->tx_pipe.dma_chan_name,
		cpsw_intf->tx_pipe.dma_channel,
		cpsw_intf->tx_pipe.dma_psflags);

	/* initialize host and slave ports */
	if (atomic_inc_return(&cpsw_dev->ale_refcnt) == 1) {
		ret = cpsw_init_ale(cpsw_dev, cpsw_intf);
		if (ret < 0) {
			atomic_dec(&cpsw_dev->ale_refcnt);
			goto ale_fail;
		}
		cpsw_init_host_port(cpsw_dev, cpsw_intf);
	}

	for_each_slave(cpsw_intf, cpsw_slave_init, cpsw_dev);

	for_each_slave(cpsw_intf, cpsw_slave_stop, cpsw_dev);

	/* Enable correct MII mode at SS level */
	for (i = 0; i < cpsw_dev->num_slaves; i++)
		if (cpsw_dev->link[i] >= XGMII_LINK_MAC_PHY)
			xgmii_mode |= (1 << i);
	__raw_writel(xgmii_mode, &cpsw_dev->ss_regs->control);

	/* disable priority elevation and enable statistics on all ports */
	__raw_writel(0, &cpsw_dev->regs->ptype);

	/* Control register */
	__raw_writel(CPSW_CTL_P0_ENABLE, &cpsw_dev->regs->control);

	/* All statistics enabled by default */
	__raw_writel(CPSW_REG_VAL_STAT_ENABLE_ALL,
		     &cpsw_dev->regs->stat_port_en);

	for_each_slave(cpsw_intf, cpsw_slave_open, cpsw_intf);

	init_timer(&cpsw_intf->timer);
	cpsw_intf->timer.data		= (unsigned long)cpsw_intf;
	cpsw_intf->timer.function	= cpswx_timer;
	cpsw_intf->timer.expires	= jiffies + CPSW_TIMER_INTERVAL;
	add_timer(&cpsw_intf->timer);
	dev_dbg(cpsw_dev->dev,
		"%s(): cpswx_timer = %p\n", __func__, cpswx_timer);

	netcp_register_txhook(netcp, CPSW_TXHOOK_ORDER,
			      cpsw_tx_hook, cpsw_intf);

#if 0
	/* Configure the streaming switch */
#define	PSTREAM_ROUTE_DMA	6
	netcp_set_streaming_switch(cpsw_dev->netcp_device, netcp->cpsw_port,
				   PSTREAM_ROUTE_DMA);
#endif

	cpsw_dev->opened = 1;
	return 0;

ale_fail:
	netcp_txpipe_close(&cpsw_intf->tx_pipe);
txpipe_fail:
	clk_disable_unprepare(cpsw_dev->clk);
clk_fail:
	clk_put(cpsw_dev->clk);
	cpsw_dev->clk = NULL;
	return ret;
}

static int cpswx_close(void *intf_modpriv, struct net_device *ndev)
{
	struct cpswx_intf *cpsw_intf = intf_modpriv;
	struct cpswx_priv *cpsw_dev = cpsw_intf->cpsw_priv;
	struct netcp_priv *netcp = netdev_priv(ndev);

	del_timer_sync(&cpsw_intf->timer);

	for_each_slave(cpsw_intf, cpsw_slave_stop, cpsw_dev);

	if (atomic_dec_return(&cpsw_dev->ale_refcnt) == 0) {
		cpsw_ale_destroy(cpsw_dev->ale);
		cpsw_dev->ale = NULL;
	}

	netcp_unregister_txhook(netcp, CPSW_TXHOOK_ORDER, cpsw_tx_hook,
				cpsw_intf);
	netcp_txpipe_close(&cpsw_intf->tx_pipe);

	clk_disable_unprepare(cpsw_dev->clk);
	clk_put(cpsw_dev->clk);

	cpsw_dev->opened = 0;
	return 0;
}

static int cpswx_remove(struct netcp_device *netcp_device, void *inst_priv)
{
	struct cpswx_priv *cpsw_dev = inst_priv;
	struct cpswx_intf *cpsw_intf, *tmp;

	of_node_put(cpsw_dev->interfaces);

	list_for_each_entry_safe(cpsw_intf, tmp, &cpsw_dev->cpsw_intf_head,
				 cpsw_intf_list) {
		netcp_delete_interface(netcp_device, cpsw_intf->ndev);
	}
	BUG_ON(!list_empty(&cpsw_dev->cpsw_intf_head));

	iounmap(cpsw_dev->ss_regs);
	memset(cpsw_dev, 0x00, sizeof(*cpsw_dev));	/* FIXME: Poison */
	kfree(cpsw_dev);
	return 0;
}

static int init_slave(struct cpswx_priv *cpsw_dev,
		      struct device_node *node, int slave_num)
{
	int ret = 0;

	ret = of_property_read_u32(node, "link-interface",
				   &cpsw_dev->link[slave_num]);
	if (ret < 0) {
		dev_err(cpsw_dev->dev,
			"missing link-interface value"
			"defaulting to mac-phy link\n");
		cpsw_dev->link[slave_num] = XGMII_LINK_MAC_PHY;
	}

	if (cpsw_dev->link[slave_num] == XGMII_LINK_MAC_PHY)
		cpsw_dev->phy_node[slave_num] =
			of_parse_phandle(node, "phy-handle", 0);

	return 0;
}

static int cpsw_create_sysfs_entries(struct cpswx_priv *cpsw_dev)
{
	struct device *dev = cpsw_dev->dev;
	int ret;

	ret = kobject_init_and_add(&cpsw_dev->kobj, &cpsw_ktype,
		kobject_get(&dev->kobj), "cpsw");

	if (ret) {
		dev_err(dev, "failed to create cpsw sysfs entry\n");
		kobject_put(&cpsw_dev->kobj);
		kobject_put(&dev->kobj);
		return ret;
	}

	ret = kobject_init_and_add(&cpsw_dev->tx_pri_kobj,
		&cpsw_tx_pri_ktype,
		kobject_get(&cpsw_dev->kobj), "port_tx_pri_map");

	if (ret) {
		dev_err(dev, "failed to create sysfs port_tx_pri_map entry\n");
		kobject_put(&cpsw_dev->tx_pri_kobj);
		kobject_put(&cpsw_dev->kobj);
		return ret;
	}

	ret = kobject_init_and_add(&cpsw_dev->pvlan_kobj,
		&cpsw_pvlan_ktype,
		kobject_get(&cpsw_dev->kobj), "port_vlan");

	if (ret) {
		dev_err(dev, "failed to create sysfs port_vlan entry\n");
		kobject_put(&cpsw_dev->pvlan_kobj);
		kobject_put(&cpsw_dev->kobj);
		return ret;
	}

	ret = kobject_init_and_add(&cpsw_dev->stats_kobj,
		&cpsw_stats_ktype,
		kobject_get(&cpsw_dev->kobj), "stats");

	if (ret) {
		dev_err(dev, "failed to create sysfs stats entry\n");
		kobject_put(&cpsw_dev->stats_kobj);
		kobject_put(&cpsw_dev->kobj);
		return ret;
	}

	return 0;
}


static int cpswx_probe(struct netcp_device *netcp_device,
			struct device *dev,
			struct device_node *node,
			void **inst_priv)
{
	struct cpswx_priv *cpsw_dev;
	struct device_node *slaves, *slave, *interfaces;
	void __iomem *regs = NULL;
	struct net_device *ndev;
	int slave_num = 0;
	int i, ret = 0;
	u32 temp[4];

	cpsw_dev = devm_kzalloc(dev, sizeof(struct cpswx_priv), GFP_KERNEL);
	if (!cpsw_dev) {
		dev_err(dev, "cpsw_dev memory allocation failed\n");
		return -ENOMEM;
	}
	*inst_priv = cpsw_dev;
	dev_dbg(dev, "%s(): cpsw_priv = %p\n", __func__, cpsw_dev);

	if (!node) {
		dev_err(dev, "device tree info unavailable\n");
		ret = -ENODEV;
		goto exit;
	}

	cpsw_dev->dev = dev;
	cpsw_dev->netcp_device = netcp_device;

	ret = of_property_read_u32(node, "serdes_at_probe",
				   &cpsw_dev->init_serdes_at_probe);
	if (ret < 0) {
		dev_err(dev,
			"missing serdes_at_probe parameter, err %d\n", ret);
		cpsw_dev->init_serdes_at_probe = 0;
	}
	dev_dbg(dev, "serdes_at_probe %u\n", cpsw_dev->init_serdes_at_probe);

	ret = of_property_read_u32(node, "sgmii_module_ofs",
				   &cpsw_dev->sgmii_module_ofs);
	if (ret < 0)
		dev_err(dev, "missing sgmii module offset, err %d\n", ret);

	ret = of_property_read_u32(node, "pcsr_module_ofs",
				   &cpsw_dev->pcsr_module_ofs);
	if (ret < 0)
		dev_err(dev, "missing pcsr module offset, err %d\n", ret);

	ret = of_property_read_u32(node, "switch_module_ofs",
				   &cpsw_dev->switch_module_ofs);
	if (ret < 0)
		dev_err(dev, "missing switch module offset, err %d\n", ret);

	ret = of_property_read_u32(node, "host_port_reg_ofs",
				   &cpsw_dev->host_port_reg_ofs);
	if (ret < 0)
		dev_err(dev, "missing host port reg offset, err %d\n", ret);

	ret = of_property_read_u32(node, "slave_reg_ofs",
				   &cpsw_dev->slave_reg_ofs);
	if (ret < 0)
		dev_err(dev, "missing slave reg offset, err %d\n", ret);

	ret = of_property_read_u32(node, "sliver_reg_ofs",
				   &cpsw_dev->sliver_reg_ofs);
	if (ret < 0)
		dev_err(dev, "missing sliver reg offset, err %d\n", ret);

	ret = of_property_read_u32(node, "hw_stats_reg_ofs",
				   &cpsw_dev->hw_stats_reg_ofs);
	if (ret < 0)
		dev_err(dev, "missing hw stats reg offset, err %d\n", ret);

	ret = of_property_read_u32(node, "ale_reg_ofs",
				   &cpsw_dev->ale_reg_ofs);
	if (ret < 0)
		dev_err(dev, "missing ale reg offset, err %d\n", ret);


	ret = of_property_read_u32(node, "num_slaves", &cpsw_dev->num_slaves);
	if (ret < 0) {
		dev_err(dev, "missing num_slaves parameter, err %d\n", ret);
		cpsw_dev->num_slaves = 2;
	}

	ret = of_property_read_u32(node, "ale_ageout", &cpsw_dev->ale_ageout);
	if (ret < 0) {
		dev_err(dev, "missing ale_ageout parameter, err %d\n", ret);
		cpsw_dev->ale_ageout = 10;
	}

	ret = of_property_read_u32(node, "ale_entries", &cpsw_dev->ale_entries);
	if (ret < 0) {
		dev_err(dev, "missing ale_entries parameter, err %d\n", ret);
		cpsw_dev->ale_entries = 1024;
	}

	ret = of_property_read_u32(node, "ale_ports", &cpsw_dev->ale_ports);
	if (ret < 0) {
		dev_err(dev, "missing ale_ports parameter, err %d\n", ret);
		cpsw_dev->ale_ports = 2;
	}

	ret = of_property_read_u32(node,
				   "intf_tx_queues", &cpsw_dev->intf_tx_queues);
	if (ret < 0) {
		dev_err(dev, "missing intf_tx_queues parameter, err %d\n", ret);
		cpsw_dev->intf_tx_queues = 1;
	}

	if (of_find_property(node, "multi-interface", NULL))
		cpsw_dev->multi_if = 1;

	ret = of_property_read_u32(node, "num-interfaces",
				   &cpsw_dev->num_interfaces);
	if (ret < 0) {
		dev_err(dev, "missing num-interfaces parameter\n");
		cpsw_dev->num_interfaces = 1;
	}

	ret = of_property_read_u32(node, "slaves-per-interface",
				   &cpsw_dev->slaves_per_interface);
	if (ret < 0) {
		dev_err(dev, "missing slaves-per_interface parameter\n");
		cpsw_dev->slaves_per_interface = 2;
	}

	/* Sub-sys regs base */
	if (of_property_read_u32_array(node, "reg", (u32 *)&(temp[0]), 2))
		dev_err(dev, "No reg defined\n");
	else
		regs = ioremap(temp[0], temp[1]);
	BUG_ON(!regs);

	cpsw_dev->ss_regs = regs;
	cpsw_dev->sgmii_port_regs	= regs + cpsw_dev->sgmii_module_ofs;
	cpsw_dev->pcsr_port_regs	= regs + cpsw_dev->pcsr_module_ofs;
	cpsw_dev->regs			= regs + cpsw_dev->switch_module_ofs;
	cpsw_dev->host_port_regs	= regs + cpsw_dev->host_port_reg_ofs;
	cpsw_dev->host_hw_stats_regs	= regs + cpsw_dev->hw_stats_reg_ofs;
	cpsw_dev->hw_stats_regs[0] = regs + cpsw_dev->hw_stats_reg_ofs + 0x100;
	cpsw_dev->hw_stats_regs[1] = regs + cpsw_dev->hw_stats_reg_ofs + 0x200;
	cpsw_dev->ale_reg		= regs + cpsw_dev->ale_reg_ofs;

	ret = of_property_read_u32(node, "host_port", &cpsw_dev->host_port);
	if (ret < 0) {
		dev_err(dev, "missing host_port parameter\n");
		cpsw_dev->host_port = 0;
	}
	cpsw_dev->rx_packet_max = NETCP_MAX_FRAME_SIZE;

	dev_dbg(dev, "num_slaves = %d\n", cpsw_dev->num_slaves);
	dev_dbg(dev, "ale_ageout = %d\n", cpsw_dev->ale_ageout);
	dev_dbg(dev, "ale_entries = %d\n", cpsw_dev->ale_entries);
	dev_dbg(dev, "ale_ports = %d\n", cpsw_dev->ale_ports);

	slaves = of_get_child_by_name(node, "slaves");
	if (!slaves) {
		dev_err(dev, "could not find slaves\n");
		ret = -ENODEV;
		goto exit;
	}

	for_each_child_of_node(slaves, slave) {
		init_slave(cpsw_dev, slave, slave_num);
		slave_num++;
	}

	of_node_put(slaves);

	interfaces = of_get_child_by_name(node, "interfaces");
	if (!interfaces)
		dev_err(dev, "could not find interfaces\n");

	cpsw_dev->interfaces = interfaces;

	cpsw_dev->serdes = of_get_child_by_name(node, "serdes");

	if (cpsw_dev->init_serdes_at_probe == 1) {
		cpsw_dev->clk = clk_get(cpsw_dev->dev, "clk_xge");
		if (IS_ERR(cpsw_dev->clk)) {
			ret = PTR_ERR(cpsw_dev->clk);
			cpsw_dev->clk = NULL;
			dev_err(cpsw_dev->dev,
				"unable to get Keystone XGE clock: %d\n", ret);
			return ret;
		}

		ret = clk_prepare_enable(cpsw_dev->clk);
		if (ret)
			goto exit;

		/* needs the serdes pll to acces switch regs */
		ret = xge_serdes_init(cpsw_dev->serdes);
		if (ret)
			goto exit;
	}

	/* Create the interface */
	INIT_LIST_HEAD(&cpsw_dev->cpsw_intf_head);
	if (cpsw_dev->multi_if)
		for (i = 0; i < cpsw_dev->num_interfaces; i++)
			netcp_create_interface(netcp_device, &ndev,
					       NULL, cpsw_dev->intf_tx_queues,
					       1, (i + 1));
	else
		netcp_create_interface(netcp_device, &ndev,
					       NULL, cpsw_dev->intf_tx_queues,
					       1, 0);

	/* init the hw stats lock */
	spin_lock_init(&cpsw_dev->hw_stats_lock);

	ret = cpsw_create_sysfs_entries(cpsw_dev);
	if (ret)
		goto exit;

	return 0;

exit:
	if (cpsw_dev->ss_regs)
		iounmap(cpsw_dev->ss_regs);
	*inst_priv = NULL;
	kfree(cpsw_dev);
	return ret;
}

static int cpswx_attach_serdes(struct cpswx_slave *slave,
			       struct cpswx_intf *cpsw_intf)
{
	struct cpswx_priv *cpsw_dev = cpsw_intf->cpsw_priv;
	phy_interface_t phy_mode;
	int has_phy = 0;
	int ret;

	if (slave->link_interface == SGMII_LINK_MAC_PHY) {
		has_phy = 1;
		phy_mode = PHY_INTERFACE_MODE_SGMII;
		slave->phy_port_t = PORT_MII;
	} else if (slave->link_interface == XGMII_LINK_MAC_PHY) {
		has_phy = 1;
		/* +++FIXME: PHY_INTERFACE_MODE_XGMII ?? */
		phy_mode = PHY_INTERFACE_MODE_NA;
		slave->phy_port_t = PORT_FIBRE;
	}

	if (has_phy) {
		/* init the PHY to facilitate serdes link training */
		slave->phy = of_phy_connect(cpsw_intf->ndev,
					    cpsw_intf->phy_node,
					    &cpsw_adjust_link, 0,
					    phy_mode,
					    slave);
	}

	ret = xge_serdes_init(cpsw_dev->serdes);
	return ret;
}

static int cpswx_attach(void *inst_priv, struct net_device *ndev,
		       void **intf_priv)
{
	struct cpswx_priv *cpsw_dev = inst_priv;
	struct cpswx_intf *cpsw_intf;
	struct netcp_priv *netcp = netdev_priv(ndev);
	struct device_node *interface;
	int i = 0, ret = 0;
	char node_name[24];

	cpsw_intf = devm_kzalloc(cpsw_dev->dev,
				 sizeof(struct cpswx_intf), GFP_KERNEL);
	if (!cpsw_intf) {
		dev_err(cpsw_dev->dev,
			"cpswx interface memory allocation failed\n");
		return -ENOMEM;
	}
	cpsw_intf->ndev = ndev;
	cpsw_intf->dev = cpsw_dev->dev;
	cpsw_intf->cpsw_priv = cpsw_dev;
	cpsw_intf->multi_if = cpsw_dev->multi_if;

	if (cpsw_dev->multi_if)
		snprintf(node_name, sizeof(node_name), "interface-%d",
			 netcp->cpsw_port - 1);
	else
		snprintf(node_name, sizeof(node_name), "interface-%d",
			 0);

	interface = of_get_child_by_name(cpsw_dev->interfaces, node_name);
	if (!interface) {
		dev_err(cpsw_dev->dev, "interface data not available\n");
		devm_kfree(cpsw_dev->dev, cpsw_intf);
		return -ENODEV;
	}
	ret = of_property_read_u32(interface, "slave_port",
				   &cpsw_intf->slave_port);
	if (ret < 0) {
		dev_err(cpsw_dev->dev, "missing slave_port paramater\n");
		return -EINVAL;
	}

	ret = of_property_read_string(interface, "tx-channel",
				      &cpsw_intf->tx_chan_name);
	if (ret < 0) {
		dev_err(cpsw_dev->dev,
			"missing tx-channel parameter, err %d\n", ret);
		cpsw_intf->tx_chan_name = "nettx";
	}
	dev_info(cpsw_dev->dev, "dma_chan_name %s\n", cpsw_intf->tx_chan_name);

	ret = of_property_read_u32(interface, "tx_queue_depth",
				   &cpsw_intf->tx_queue_depth);
	if (ret < 0) {
		dev_err(cpsw_dev->dev,
			"missing tx_queue_depth parameter, err %d\n", ret);
		cpsw_intf->tx_queue_depth = 32;
	}
	dev_dbg(cpsw_dev->dev, "tx_queue_depth %u\n",
		cpsw_intf->tx_queue_depth);

	of_node_put(interface);

	cpsw_intf->num_slaves = cpsw_dev->slaves_per_interface;

	cpsw_intf->slaves = devm_kzalloc(cpsw_dev->dev,
					 sizeof(struct cpswx_slave) *
					 cpsw_intf->num_slaves, GFP_KERNEL);

	if (!cpsw_intf->slaves) {
		dev_err(cpsw_dev->dev,
			"cpsw interface slave memory allocation failed\n");
		devm_kfree(cpsw_dev->dev, cpsw_intf);
		return -ENOMEM;
	}

	if (cpsw_dev->multi_if) {
		cpsw_intf->slaves[i].slave_num = cpsw_intf->slave_port;
		cpsw_intf->slaves[i].link_interface =
			cpsw_dev->link[cpsw_intf->slave_port];
		cpsw_intf->phy_node = cpsw_dev->phy_node[cpsw_intf->slave_port];
	} else {
		for (i = 0; i < cpsw_intf->num_slaves; i++) {
			cpsw_intf->slaves[i].slave_num = i;
			cpsw_intf->slaves[i].link_interface = cpsw_dev->link[i];
		}
	}

	netcp_txpipe_init(&cpsw_intf->tx_pipe, netdev_priv(ndev),
			  cpsw_intf->tx_chan_name, cpsw_intf->tx_queue_depth);

	cpsw_intf->tx_pipe.dma_psflags	= netcp->cpsw_port;

	SET_ETHTOOL_OPS(ndev, &keystone_ethtool_ops);

	list_add(&cpsw_intf->cpsw_intf_list, &cpsw_dev->cpsw_intf_head);

	*intf_priv = cpsw_intf;

	for_each_slave(cpsw_intf, cpswx_attach_serdes, cpsw_intf);
	return ret;
}

static int cpswx_release(void *intf_modpriv)
{
	struct cpswx_intf *cpsw_intf = intf_modpriv;

	SET_ETHTOOL_OPS(cpsw_intf->ndev, NULL);

	list_del(&cpsw_intf->cpsw_intf_list);

	devm_kfree(cpsw_intf->dev, cpsw_intf->slaves);
	devm_kfree(cpsw_intf->dev, cpsw_intf);

	return 0;
}


static struct netcp_module cpsw_module = {
	.name		= CPSW_MODULE_NAME,
	.owner		= THIS_MODULE,
	.probe		= cpswx_probe,
	.open		= cpswx_open,
	.close		= cpswx_close,
	.remove		= cpswx_remove,
	.attach		= cpswx_attach,
	.release	= cpswx_release,
	.add_addr	= cpswx_add_addr,
	.del_addr	= cpswx_del_addr,
	.ioctl		= cpswx_ioctl,
};

int __init keystone_cpswx_init(void)
{
	return netcp_register_module(&cpsw_module);
}

void __exit keystone_cpswx_exit(void)
{
	netcp_unregister_module(&cpsw_module);
}

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Sandeep Paulraj <s-paulraj@ti.com>");
MODULE_DESCRIPTION("CPSW driver for Keystone 10GE devices");