diff options
Diffstat (limited to 'drivers/net/ethernet/xilinx')
| -rw-r--r-- | drivers/net/ethernet/xilinx/Kconfig | 44 | ||||
| -rw-r--r-- | drivers/net/ethernet/xilinx/Makefile | 9 | ||||
| -rw-r--r-- | drivers/net/ethernet/xilinx/ll_temac.h | 385 | ||||
| -rw-r--r-- | drivers/net/ethernet/xilinx/ll_temac_main.c | 1184 | ||||
| -rw-r--r-- | drivers/net/ethernet/xilinx/ll_temac_mdio.c | 122 | ||||
| -rw-r--r-- | drivers/net/ethernet/xilinx/xilinx_axienet.h | 506 | ||||
| -rw-r--r-- | drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 1659 | ||||
| -rw-r--r-- | drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c | 239 | ||||
| -rw-r--r-- | drivers/net/ethernet/xilinx/xilinx_emaclite.c | 1259 | 
9 files changed, 5407 insertions, 0 deletions
diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig new file mode 100644 index 00000000000..7b90a5eba09 --- /dev/null +++ b/drivers/net/ethernet/xilinx/Kconfig @@ -0,0 +1,44 @@ +# +# Xilink device configuration +# + +config NET_VENDOR_XILINX +	bool "Xilinx devices" +	default y +	depends on PPC || PPC32 || MICROBLAZE || ARCH_ZYNQ +	---help--- +	  If you have a network (Ethernet) card belonging to this class, say Y +	  and read the Ethernet-HOWTO, available from +	  <http://www.tldp.org/docs.html#howto>. + +	  Note that the answer to this question doesn't directly affect the +	  kernel: saying N will just cause the configurator to skip all +	  the questions about Xilinx devices. If you say Y, you will be asked +	  for your specific card in the following questions. + +if NET_VENDOR_XILINX + +config XILINX_EMACLITE +	tristate "Xilinx 10/100 Ethernet Lite support" +	depends on (PPC32 || MICROBLAZE || ARCH_ZYNQ) +	select PHYLIB +	---help--- +	  This driver supports the 10/100 Ethernet Lite from Xilinx. + +config XILINX_AXI_EMAC +	tristate "Xilinx 10/100/1000 AXI Ethernet support" +	depends on MICROBLAZE +	select PHYLIB +	---help--- +	  This driver supports the 10/100/1000 Ethernet from Xilinx for the +	  AXI bus interface used in Xilinx Virtex FPGAs. + +config XILINX_LL_TEMAC +	tristate "Xilinx LL TEMAC (LocalLink Tri-mode Ethernet MAC) driver" +	depends on (PPC || MICROBLAZE) +	select PHYLIB +	---help--- +	  This driver supports the Xilinx 10/100/1000 LocalLink TEMAC +	  core used in Xilinx Spartan and Virtex FPGAs + +endif # NET_VENDOR_XILINX diff --git a/drivers/net/ethernet/xilinx/Makefile b/drivers/net/ethernet/xilinx/Makefile new file mode 100644 index 00000000000..214205e975e --- /dev/null +++ b/drivers/net/ethernet/xilinx/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the Xilink network device drivers. +# + +ll_temac-objs := ll_temac_main.o ll_temac_mdio.o +obj-$(CONFIG_XILINX_LL_TEMAC) += ll_temac.o +obj-$(CONFIG_XILINX_EMACLITE) += xilinx_emaclite.o +xilinx_emac-objs := xilinx_axienet_main.o xilinx_axienet_mdio.o +obj-$(CONFIG_XILINX_AXI_EMAC) += xilinx_emac.o diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h new file mode 100644 index 00000000000..522abe2ff25 --- /dev/null +++ b/drivers/net/ethernet/xilinx/ll_temac.h @@ -0,0 +1,385 @@ + +#ifndef XILINX_LL_TEMAC_H +#define XILINX_LL_TEMAC_H + +#include <linux/netdevice.h> +#include <linux/of.h> +#include <linux/spinlock.h> + +#ifdef CONFIG_PPC_DCR +#include <asm/dcr.h> +#include <asm/dcr-regs.h> +#endif + +/* packet size info */ +#define XTE_HDR_SIZE			14      /* size of Ethernet header */ +#define XTE_TRL_SIZE			4       /* size of Ethernet trailer (FCS) */ +#define XTE_JUMBO_MTU			9000 +#define XTE_MAX_JUMBO_FRAME_SIZE	(XTE_JUMBO_MTU + XTE_HDR_SIZE + XTE_TRL_SIZE) + +/*  Configuration options */ + +/*  Accept all incoming packets. + *  This option defaults to disabled (cleared) */ +#define XTE_OPTION_PROMISC                      (1 << 0) +/*  Jumbo frame support for Tx & Rx. + *  This option defaults to disabled (cleared) */ +#define XTE_OPTION_JUMBO                        (1 << 1) +/*  VLAN Rx & Tx frame support. + *  This option defaults to disabled (cleared) */ +#define XTE_OPTION_VLAN                         (1 << 2) +/*  Enable recognition of flow control frames on Rx + *  This option defaults to enabled (set) */ +#define XTE_OPTION_FLOW_CONTROL                 (1 << 4) +/*  Strip FCS and PAD from incoming frames. + *  Note: PAD from VLAN frames is not stripped. + *  This option defaults to disabled (set) */ +#define XTE_OPTION_FCS_STRIP                    (1 << 5) +/*  Generate FCS field and add PAD automatically for outgoing frames. + *  This option defaults to enabled (set) */ +#define XTE_OPTION_FCS_INSERT                   (1 << 6) +/*  Enable Length/Type error checking for incoming frames. When this option is +set, the MAC will filter frames that have a mismatched type/length field +and if XTE_OPTION_REPORT_RXERR is set, the user is notified when these +types of frames are encountered. When this option is cleared, the MAC will +allow these types of frames to be received. +This option defaults to enabled (set) */ +#define XTE_OPTION_LENTYPE_ERR                  (1 << 7) +/*  Enable the transmitter. + *  This option defaults to enabled (set) */ +#define XTE_OPTION_TXEN                         (1 << 11) +/*  Enable the receiver +*   This option defaults to enabled (set) */ +#define XTE_OPTION_RXEN                         (1 << 12) + +/*  Default options set when device is initialized or reset */ +#define XTE_OPTION_DEFAULTS                     \ +	(XTE_OPTION_TXEN |                          \ +	 XTE_OPTION_FLOW_CONTROL |                  \ +	 XTE_OPTION_RXEN) + +/* XPS_LL_TEMAC SDMA registers definition */ + +#define TX_NXTDESC_PTR      0x00            /* r */ +#define TX_CURBUF_ADDR      0x01            /* r */ +#define TX_CURBUF_LENGTH    0x02            /* r */ +#define TX_CURDESC_PTR      0x03            /* rw */ +#define TX_TAILDESC_PTR     0x04            /* rw */ +#define TX_CHNL_CTRL        0x05            /* rw */ +/* + 0:7      24:31       IRQTimeout + 8:15     16:23       IRQCount + 16:20    11:15       Reserved + 21       10          0 + 22       9           UseIntOnEnd + 23       8           LdIRQCnt + 24       7           IRQEn + 25:28    3:6         Reserved + 29       2           IrqErrEn + 30       1           IrqDlyEn + 31       0           IrqCoalEn +*/ +#define CHNL_CTRL_IRQ_IOE       (1 << 9) +#define CHNL_CTRL_IRQ_EN        (1 << 7) +#define CHNL_CTRL_IRQ_ERR_EN    (1 << 2) +#define CHNL_CTRL_IRQ_DLY_EN    (1 << 1) +#define CHNL_CTRL_IRQ_COAL_EN   (1 << 0) +#define TX_IRQ_REG          0x06            /* rw */ +/* +  0:7      24:31       DltTmrValue + 8:15     16:23       ClscCntrValue + 16:17    14:15       Reserved + 18:21    10:13       ClscCnt + 22:23    8:9         DlyCnt + 24:28    3::7        Reserved + 29       2           ErrIrq + 30       1           DlyIrq + 31       0           CoalIrq + */ +#define TX_CHNL_STS         0x07            /* r */ +/* +   0:9      22:31   Reserved + 10       21      TailPErr + 11       20      CmpErr + 12       19      AddrErr + 13       18      NxtPErr + 14       17      CurPErr + 15       16      BsyWr + 16:23    8:15    Reserved + 24       7       Error + 25       6       IOE + 26       5       SOE + 27       4       Cmplt + 28       3       SOP + 29       2       EOP + 30       1       EngBusy + 31       0       Reserved +*/ + +#define RX_NXTDESC_PTR      0x08            /* r */ +#define RX_CURBUF_ADDR      0x09            /* r */ +#define RX_CURBUF_LENGTH    0x0a            /* r */ +#define RX_CURDESC_PTR      0x0b            /* rw */ +#define RX_TAILDESC_PTR     0x0c            /* rw */ +#define RX_CHNL_CTRL        0x0d            /* rw */ +/* + 0:7      24:31       IRQTimeout + 8:15     16:23       IRQCount + 16:20    11:15       Reserved + 21       10          0 + 22       9           UseIntOnEnd + 23       8           LdIRQCnt + 24       7           IRQEn + 25:28    3:6         Reserved + 29       2           IrqErrEn + 30       1           IrqDlyEn + 31       0           IrqCoalEn + */ +#define RX_IRQ_REG          0x0e            /* rw */ +#define IRQ_COAL        (1 << 0) +#define IRQ_DLY         (1 << 1) +#define IRQ_ERR         (1 << 2) +#define IRQ_DMAERR      (1 << 7)            /* this is not documented ??? */ +/* + 0:7      24:31       DltTmrValue + 8:15     16:23       ClscCntrValue + 16:17    14:15       Reserved + 18:21    10:13       ClscCnt + 22:23    8:9         DlyCnt + 24:28    3::7        Reserved +*/ +#define RX_CHNL_STS         0x0f        /* r */ +#define CHNL_STS_ENGBUSY    (1 << 1) +#define CHNL_STS_EOP        (1 << 2) +#define CHNL_STS_SOP        (1 << 3) +#define CHNL_STS_CMPLT      (1 << 4) +#define CHNL_STS_SOE        (1 << 5) +#define CHNL_STS_IOE        (1 << 6) +#define CHNL_STS_ERR        (1 << 7) + +#define CHNL_STS_BSYWR      (1 << 16) +#define CHNL_STS_CURPERR    (1 << 17) +#define CHNL_STS_NXTPERR    (1 << 18) +#define CHNL_STS_ADDRERR    (1 << 19) +#define CHNL_STS_CMPERR     (1 << 20) +#define CHNL_STS_TAILERR    (1 << 21) +/* + 0:9      22:31   Reserved + 10       21      TailPErr + 11       20      CmpErr + 12       19      AddrErr + 13       18      NxtPErr + 14       17      CurPErr + 15       16      BsyWr + 16:23    8:15    Reserved + 24       7       Error + 25       6       IOE + 26       5       SOE + 27       4       Cmplt + 28       3       SOP + 29       2       EOP + 30       1       EngBusy + 31       0       Reserved +*/ + +#define DMA_CONTROL_REG             0x10            /* rw */ +#define DMA_CONTROL_RST                 (1 << 0) +#define DMA_TAIL_ENABLE                 (1 << 2) + +/* XPS_LL_TEMAC direct registers definition */ + +#define XTE_RAF0_OFFSET              0x00 +#define RAF0_RST                        (1 << 0) +#define RAF0_MCSTREJ                    (1 << 1) +#define RAF0_BCSTREJ                    (1 << 2) +#define XTE_TPF0_OFFSET              0x04 +#define XTE_IFGP0_OFFSET             0x08 +#define XTE_ISR0_OFFSET              0x0c +#define ISR0_HARDACSCMPLT               (1 << 0) +#define ISR0_AUTONEG                    (1 << 1) +#define ISR0_RXCMPLT                    (1 << 2) +#define ISR0_RXREJ                      (1 << 3) +#define ISR0_RXFIFOOVR                  (1 << 4) +#define ISR0_TXCMPLT                    (1 << 5) +#define ISR0_RXDCMLCK                   (1 << 6) + +#define XTE_IPR0_OFFSET              0x10 +#define XTE_IER0_OFFSET              0x14 + +#define XTE_MSW0_OFFSET              0x20 +#define XTE_LSW0_OFFSET              0x24 +#define XTE_CTL0_OFFSET              0x28 +#define XTE_RDY0_OFFSET              0x2c + +#define XTE_RSE_MIIM_RR_MASK      0x0002 +#define XTE_RSE_MIIM_WR_MASK      0x0004 +#define XTE_RSE_CFG_RR_MASK       0x0020 +#define XTE_RSE_CFG_WR_MASK       0x0040 +#define XTE_RDY0_HARD_ACS_RDY_MASK  (0x10000) + +/* XPS_LL_TEMAC indirect registers offset definition */ + +#define	XTE_RXC0_OFFSET			0x00000200 /* Rx configuration word 0 */ +#define	XTE_RXC1_OFFSET			0x00000240 /* Rx configuration word 1 */ +#define XTE_RXC1_RXRST_MASK		(1 << 31)  /* Receiver reset */ +#define XTE_RXC1_RXJMBO_MASK		(1 << 30)  /* Jumbo frame enable */ +#define XTE_RXC1_RXFCS_MASK		(1 << 29)  /* FCS not stripped */ +#define XTE_RXC1_RXEN_MASK		(1 << 28)  /* Receiver enable */ +#define XTE_RXC1_RXVLAN_MASK		(1 << 27)  /* VLAN enable */ +#define XTE_RXC1_RXHD_MASK		(1 << 26)  /* Half duplex */ +#define XTE_RXC1_RXLT_MASK		(1 << 25)  /* Length/type check disable */ + +#define XTE_TXC_OFFSET			0x00000280 /*  Tx configuration */ +#define XTE_TXC_TXRST_MASK		(1 << 31)  /* Transmitter reset */ +#define XTE_TXC_TXJMBO_MASK		(1 << 30)  /* Jumbo frame enable */ +#define XTE_TXC_TXFCS_MASK		(1 << 29)  /* Generate FCS */ +#define XTE_TXC_TXEN_MASK		(1 << 28)  /* Transmitter enable */ +#define XTE_TXC_TXVLAN_MASK		(1 << 27)  /* VLAN enable */ +#define XTE_TXC_TXHD_MASK		(1 << 26)  /* Half duplex */ + +#define XTE_FCC_OFFSET			0x000002C0 /* Flow control config */ +#define XTE_FCC_RXFLO_MASK		(1 << 29)  /* Rx flow control enable */ +#define XTE_FCC_TXFLO_MASK		(1 << 30)  /* Tx flow control enable */ + +#define XTE_EMCFG_OFFSET		0x00000300 /* EMAC configuration */ +#define XTE_EMCFG_LINKSPD_MASK		0xC0000000 /* Link speed */ +#define XTE_EMCFG_HOSTEN_MASK		(1 << 26)  /* Host interface enable */ +#define XTE_EMCFG_LINKSPD_10		0x00000000 /* 10 Mbit LINKSPD_MASK */ +#define XTE_EMCFG_LINKSPD_100		(1 << 30)  /* 100 Mbit LINKSPD_MASK */ +#define XTE_EMCFG_LINKSPD_1000		(1 << 31)  /* 1000 Mbit LINKSPD_MASK */ + +#define XTE_GMIC_OFFSET			0x00000320 /* RGMII/SGMII config */ +#define XTE_MC_OFFSET			0x00000340 /* MDIO configuration */ +#define XTE_UAW0_OFFSET			0x00000380 /* Unicast address word 0 */ +#define XTE_UAW1_OFFSET			0x00000384 /* Unicast address word 1 */ + +#define XTE_MAW0_OFFSET			0x00000388 /* Multicast addr word 0 */ +#define XTE_MAW1_OFFSET			0x0000038C /* Multicast addr word 1 */ +#define XTE_AFM_OFFSET			0x00000390 /* Promiscuous mode */ +#define XTE_AFM_EPPRM_MASK		(1 << 31)  /* Promiscuous mode enable */ + +/* Interrupt Request status */ +#define XTE_TIS_OFFSET			0x000003A0 +#define TIS_FRIS			(1 << 0) +#define TIS_MRIS			(1 << 1) +#define TIS_MWIS			(1 << 2) +#define TIS_ARIS			(1 << 3) +#define TIS_AWIS			(1 << 4) +#define TIS_CRIS			(1 << 5) +#define TIS_CWIS			(1 << 6) + +#define XTE_TIE_OFFSET			0x000003A4 /* Interrupt enable */ + +/**  MII Mamagement Control register (MGTCR) */ +#define XTE_MGTDR_OFFSET		0x000003B0 /* MII data */ +#define XTE_MIIMAI_OFFSET		0x000003B4 /* MII control */ + +#define CNTLREG_WRITE_ENABLE_MASK   0x8000 +#define CNTLREG_EMAC1SEL_MASK       0x0400 +#define CNTLREG_ADDRESSCODE_MASK    0x03ff + +/* CDMAC descriptor status bit definitions */ + +#define STS_CTRL_APP0_ERR         (1 << 31) +#define STS_CTRL_APP0_IRQONEND    (1 << 30) +/* undoccumented */ +#define STS_CTRL_APP0_STOPONEND   (1 << 29) +#define STS_CTRL_APP0_CMPLT       (1 << 28) +#define STS_CTRL_APP0_SOP         (1 << 27) +#define STS_CTRL_APP0_EOP         (1 << 26) +#define STS_CTRL_APP0_ENGBUSY     (1 << 25) +/* undocumented */ +#define STS_CTRL_APP0_ENGRST      (1 << 24) + +#define TX_CONTROL_CALC_CSUM_MASK   1 + +#define MULTICAST_CAM_TABLE_NUM 4 + +/* TEMAC Synthesis features */ +#define TEMAC_FEATURE_RX_CSUM  (1 << 0) +#define TEMAC_FEATURE_TX_CSUM  (1 << 1) + +/* TX/RX CURDESC_PTR points to first descriptor */ +/* TX/RX TAILDESC_PTR points to last descriptor in linked list */ + +/** + * struct cdmac_bd - LocalLink buffer descriptor format + * + * app0 bits: + *	0    Error + *	1    IrqOnEnd    generate an interrupt at completion of DMA  op + *	2    reserved + *	3    completed   Current descriptor completed + *	4    SOP         TX - marks first desc/ RX marks first desct + *	5    EOP         TX marks last desc/RX marks last desc + *	6    EngBusy     DMA is processing + *	7    reserved + *	8:31 application specific + */ +struct cdmac_bd { +	u32 next;	/* Physical address of next buffer descriptor */ +	u32 phys; +	u32 len; +	u32 app0; +	u32 app1;	/* TX start << 16 | insert */ +	u32 app2;	/* TX csum */ +	u32 app3; +	u32 app4;	/* skb for TX length for RX */ +}; + +struct temac_local { +	struct net_device *ndev; +	struct device *dev; + +	/* Connection to PHY device */ +	struct phy_device *phy_dev;	/* Pointer to PHY device */ +	struct device_node *phy_node; + +	/* MDIO bus data */ +	struct mii_bus *mii_bus;	/* MII bus reference */ +	int mdio_irqs[PHY_MAX_ADDR];	/* IRQs table for MDIO bus */ + +	/* IO registers, dma functions and IRQs */ +	void __iomem *regs; +	void __iomem *sdma_regs; +#ifdef CONFIG_PPC_DCR +	dcr_host_t sdma_dcrs; +#endif +	u32 (*dma_in)(struct temac_local *, int); +	void (*dma_out)(struct temac_local *, int, u32); + +	int tx_irq; +	int rx_irq; +	int emac_num; + +	struct sk_buff **rx_skb; +	spinlock_t rx_lock; +	struct mutex indirect_mutex; +	u32 options;			/* Current options word */ +	int last_link; +	unsigned int temac_features; + +	/* Buffer descriptors */ +	struct cdmac_bd *tx_bd_v; +	dma_addr_t tx_bd_p; +	struct cdmac_bd *rx_bd_v; +	dma_addr_t rx_bd_p; +	int tx_bd_ci; +	int tx_bd_next; +	int tx_bd_tail; +	int rx_bd_ci; +}; + +/* xilinx_temac.c */ +u32 temac_ior(struct temac_local *lp, int offset); +void temac_iow(struct temac_local *lp, int offset, u32 value); +int temac_indirect_busywait(struct temac_local *lp); +u32 temac_indirect_in32(struct temac_local *lp, int reg); +void temac_indirect_out32(struct temac_local *lp, int reg, u32 value); + + +/* xilinx_temac_mdio.c */ +int temac_mdio_setup(struct temac_local *lp, struct device_node *np); +void temac_mdio_teardown(struct temac_local *lp); + +#endif /* XILINX_LL_TEMAC_H */ diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c new file mode 100644 index 00000000000..4ef818a7a6c --- /dev/null +++ b/drivers/net/ethernet/xilinx/ll_temac_main.c @@ -0,0 +1,1184 @@ +/* + * Driver for Xilinx TEMAC Ethernet device + * + * Copyright (c) 2008 Nissin Systems Co., Ltd.,  Yoshio Kashiwagi + * Copyright (c) 2005-2008 DLA Systems,  David H. Lynch Jr. <dhlii@dlasys.net> + * Copyright (c) 2008-2009 Secret Lab Technologies Ltd. + * + * This is a driver for the Xilinx ll_temac ipcore which is often used + * in the Virtex and Spartan series of chips. + * + * Notes: + * - The ll_temac hardware uses indirect access for many of the TEMAC + *   registers, include the MDIO bus.  However, indirect access to MDIO + *   registers take considerably more clock cycles than to TEMAC registers. + *   MDIO accesses are long, so threads doing them should probably sleep + *   rather than busywait.  However, since only one indirect access can be + *   in progress at any given time, that means that *all* indirect accesses + *   could end up sleeping (to wait for an MDIO access to complete). + *   Fortunately none of the indirect accesses are on the 'hot' path for tx + *   or rx, so this should be okay. + * + * TODO: + * - Factor out locallink DMA code into separate driver + * - Fix multicast assignment. + * - Fix support for hardware checksumming. + * - Testing.  Lots and lots of testing. + * + */ + +#include <linux/delay.h> +#include <linux/etherdevice.h> +#include <linux/mii.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/netdevice.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/of_mdio.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/tcp.h>      /* needed for sizeof(tcphdr) */ +#include <linux/udp.h>      /* needed for sizeof(udphdr) */ +#include <linux/phy.h> +#include <linux/in.h> +#include <linux/io.h> +#include <linux/ip.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> + +#include "ll_temac.h" + +#define TX_BD_NUM   64 +#define RX_BD_NUM   128 + +/* --------------------------------------------------------------------- + * Low level register access functions + */ + +u32 temac_ior(struct temac_local *lp, int offset) +{ +	return in_be32((u32 *)(lp->regs + offset)); +} + +void temac_iow(struct temac_local *lp, int offset, u32 value) +{ +	out_be32((u32 *) (lp->regs + offset), value); +} + +int temac_indirect_busywait(struct temac_local *lp) +{ +	long end = jiffies + 2; + +	while (!(temac_ior(lp, XTE_RDY0_OFFSET) & XTE_RDY0_HARD_ACS_RDY_MASK)) { +		if (time_before_eq(end, jiffies)) { +			WARN_ON(1); +			return -ETIMEDOUT; +		} +		msleep(1); +	} +	return 0; +} + +/** + * temac_indirect_in32 + * + * lp->indirect_mutex must be held when calling this function + */ +u32 temac_indirect_in32(struct temac_local *lp, int reg) +{ +	u32 val; + +	if (temac_indirect_busywait(lp)) +		return -ETIMEDOUT; +	temac_iow(lp, XTE_CTL0_OFFSET, reg); +	if (temac_indirect_busywait(lp)) +		return -ETIMEDOUT; +	val = temac_ior(lp, XTE_LSW0_OFFSET); + +	return val; +} + +/** + * temac_indirect_out32 + * + * lp->indirect_mutex must be held when calling this function + */ +void temac_indirect_out32(struct temac_local *lp, int reg, u32 value) +{ +	if (temac_indirect_busywait(lp)) +		return; +	temac_iow(lp, XTE_LSW0_OFFSET, value); +	temac_iow(lp, XTE_CTL0_OFFSET, CNTLREG_WRITE_ENABLE_MASK | reg); +	temac_indirect_busywait(lp); +} + +/** + * temac_dma_in32 - Memory mapped DMA read, this function expects a + * register input that is based on DCR word addresses which + * are then converted to memory mapped byte addresses + */ +static u32 temac_dma_in32(struct temac_local *lp, int reg) +{ +	return in_be32((u32 *)(lp->sdma_regs + (reg << 2))); +} + +/** + * temac_dma_out32 - Memory mapped DMA read, this function expects a + * register input that is based on DCR word addresses which + * are then converted to memory mapped byte addresses + */ +static void temac_dma_out32(struct temac_local *lp, int reg, u32 value) +{ +	out_be32((u32 *)(lp->sdma_regs + (reg << 2)), value); +} + +/* DMA register access functions can be DCR based or memory mapped. + * The PowerPC 440 is DCR based, the PowerPC 405 and MicroBlaze are both + * memory mapped. + */ +#ifdef CONFIG_PPC_DCR + +/** + * temac_dma_dcr_in32 - DCR based DMA read + */ +static u32 temac_dma_dcr_in(struct temac_local *lp, int reg) +{ +	return dcr_read(lp->sdma_dcrs, reg); +} + +/** + * temac_dma_dcr_out32 - DCR based DMA write + */ +static void temac_dma_dcr_out(struct temac_local *lp, int reg, u32 value) +{ +	dcr_write(lp->sdma_dcrs, reg, value); +} + +/** + * temac_dcr_setup - If the DMA is DCR based, then setup the address and + * I/O  functions + */ +static int temac_dcr_setup(struct temac_local *lp, struct platform_device *op, +				struct device_node *np) +{ +	unsigned int dcrs; + +	/* setup the dcr address mapping if it's in the device tree */ + +	dcrs = dcr_resource_start(np, 0); +	if (dcrs != 0) { +		lp->sdma_dcrs = dcr_map(np, dcrs, dcr_resource_len(np, 0)); +		lp->dma_in = temac_dma_dcr_in; +		lp->dma_out = temac_dma_dcr_out; +		dev_dbg(&op->dev, "DCR base: %x\n", dcrs); +		return 0; +	} +	/* no DCR in the device tree, indicate a failure */ +	return -1; +} + +#else + +/* + * temac_dcr_setup - This is a stub for when DCR is not supported, + * such as with MicroBlaze + */ +static int temac_dcr_setup(struct temac_local *lp, struct platform_device *op, +				struct device_node *np) +{ +	return -1; +} + +#endif + +/** + * temac_dma_bd_release - Release buffer descriptor rings + */ +static void temac_dma_bd_release(struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); +	int i; + +	/* Reset Local Link (DMA) */ +	lp->dma_out(lp, DMA_CONTROL_REG, DMA_CONTROL_RST); + +	for (i = 0; i < RX_BD_NUM; i++) { +		if (!lp->rx_skb[i]) +			break; +		else { +			dma_unmap_single(ndev->dev.parent, lp->rx_bd_v[i].phys, +					XTE_MAX_JUMBO_FRAME_SIZE, DMA_FROM_DEVICE); +			dev_kfree_skb(lp->rx_skb[i]); +		} +	} +	if (lp->rx_bd_v) +		dma_free_coherent(ndev->dev.parent, +				sizeof(*lp->rx_bd_v) * RX_BD_NUM, +				lp->rx_bd_v, lp->rx_bd_p); +	if (lp->tx_bd_v) +		dma_free_coherent(ndev->dev.parent, +				sizeof(*lp->tx_bd_v) * TX_BD_NUM, +				lp->tx_bd_v, lp->tx_bd_p); +	if (lp->rx_skb) +		kfree(lp->rx_skb); +} + +/** + * temac_dma_bd_init - Setup buffer descriptor rings + */ +static int temac_dma_bd_init(struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); +	struct sk_buff *skb; +	int i; + +	lp->rx_skb = kcalloc(RX_BD_NUM, sizeof(*lp->rx_skb), GFP_KERNEL); +	if (!lp->rx_skb) +		goto out; + +	/* allocate the tx and rx ring buffer descriptors. */ +	/* returns a virtual address and a physical address. */ +	lp->tx_bd_v = dma_zalloc_coherent(ndev->dev.parent, +					  sizeof(*lp->tx_bd_v) * TX_BD_NUM, +					  &lp->tx_bd_p, GFP_KERNEL); +	if (!lp->tx_bd_v) +		goto out; + +	lp->rx_bd_v = dma_zalloc_coherent(ndev->dev.parent, +					  sizeof(*lp->rx_bd_v) * RX_BD_NUM, +					  &lp->rx_bd_p, GFP_KERNEL); +	if (!lp->rx_bd_v) +		goto out; + +	for (i = 0; i < TX_BD_NUM; i++) { +		lp->tx_bd_v[i].next = lp->tx_bd_p + +				sizeof(*lp->tx_bd_v) * ((i + 1) % TX_BD_NUM); +	} + +	for (i = 0; i < RX_BD_NUM; i++) { +		lp->rx_bd_v[i].next = lp->rx_bd_p + +				sizeof(*lp->rx_bd_v) * ((i + 1) % RX_BD_NUM); + +		skb = netdev_alloc_skb_ip_align(ndev, +						XTE_MAX_JUMBO_FRAME_SIZE); +		if (!skb) +			goto out; + +		lp->rx_skb[i] = skb; +		/* returns physical address of skb->data */ +		lp->rx_bd_v[i].phys = dma_map_single(ndev->dev.parent, +						     skb->data, +						     XTE_MAX_JUMBO_FRAME_SIZE, +						     DMA_FROM_DEVICE); +		lp->rx_bd_v[i].len = XTE_MAX_JUMBO_FRAME_SIZE; +		lp->rx_bd_v[i].app0 = STS_CTRL_APP0_IRQONEND; +	} + +	lp->dma_out(lp, TX_CHNL_CTRL, 0x10220400 | +					  CHNL_CTRL_IRQ_EN | +					  CHNL_CTRL_IRQ_DLY_EN | +					  CHNL_CTRL_IRQ_COAL_EN); +	/* 0x10220483 */ +	/* 0x00100483 */ +	lp->dma_out(lp, RX_CHNL_CTRL, 0xff070000 | +					  CHNL_CTRL_IRQ_EN | +					  CHNL_CTRL_IRQ_DLY_EN | +					  CHNL_CTRL_IRQ_COAL_EN | +					  CHNL_CTRL_IRQ_IOE); +	/* 0xff010283 */ + +	lp->dma_out(lp, RX_CURDESC_PTR,  lp->rx_bd_p); +	lp->dma_out(lp, RX_TAILDESC_PTR, +		       lp->rx_bd_p + (sizeof(*lp->rx_bd_v) * (RX_BD_NUM - 1))); +	lp->dma_out(lp, TX_CURDESC_PTR, lp->tx_bd_p); + +	/* Init descriptor indexes */ +	lp->tx_bd_ci = 0; +	lp->tx_bd_next = 0; +	lp->tx_bd_tail = 0; +	lp->rx_bd_ci = 0; + +	return 0; + +out: +	temac_dma_bd_release(ndev); +	return -ENOMEM; +} + +/* --------------------------------------------------------------------- + * net_device_ops + */ + +static void temac_do_set_mac_address(struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); + +	/* set up unicast MAC address filter set its mac address */ +	mutex_lock(&lp->indirect_mutex); +	temac_indirect_out32(lp, XTE_UAW0_OFFSET, +			     (ndev->dev_addr[0]) | +			     (ndev->dev_addr[1] << 8) | +			     (ndev->dev_addr[2] << 16) | +			     (ndev->dev_addr[3] << 24)); +	/* There are reserved bits in EUAW1 +	 * so don't affect them Set MAC bits [47:32] in EUAW1 */ +	temac_indirect_out32(lp, XTE_UAW1_OFFSET, +			     (ndev->dev_addr[4] & 0x000000ff) | +			     (ndev->dev_addr[5] << 8)); +	mutex_unlock(&lp->indirect_mutex); +} + +static int temac_init_mac_address(struct net_device *ndev, void *address) +{ +	memcpy(ndev->dev_addr, address, ETH_ALEN); +	if (!is_valid_ether_addr(ndev->dev_addr)) +		eth_hw_addr_random(ndev); +	temac_do_set_mac_address(ndev); +	return 0; +} + +static int temac_set_mac_address(struct net_device *ndev, void *p) +{ +	struct sockaddr *addr = p; + +	if (!is_valid_ether_addr(addr->sa_data)) +		return -EADDRNOTAVAIL; +	memcpy(ndev->dev_addr, addr->sa_data, ETH_ALEN); +	temac_do_set_mac_address(ndev); +	return 0; +} + +static void temac_set_multicast_list(struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); +	u32 multi_addr_msw, multi_addr_lsw, val; +	int i; + +	mutex_lock(&lp->indirect_mutex); +	if (ndev->flags & (IFF_ALLMULTI | IFF_PROMISC) || +	    netdev_mc_count(ndev) > MULTICAST_CAM_TABLE_NUM) { +		/* +		 *	We must make the kernel realise we had to move +		 *	into promisc mode or we start all out war on +		 *	the cable. If it was a promisc request the +		 *	flag is already set. If not we assert it. +		 */ +		ndev->flags |= IFF_PROMISC; +		temac_indirect_out32(lp, XTE_AFM_OFFSET, XTE_AFM_EPPRM_MASK); +		dev_info(&ndev->dev, "Promiscuous mode enabled.\n"); +	} else if (!netdev_mc_empty(ndev)) { +		struct netdev_hw_addr *ha; + +		i = 0; +		netdev_for_each_mc_addr(ha, ndev) { +			if (i >= MULTICAST_CAM_TABLE_NUM) +				break; +			multi_addr_msw = ((ha->addr[3] << 24) | +					  (ha->addr[2] << 16) | +					  (ha->addr[1] << 8) | +					  (ha->addr[0])); +			temac_indirect_out32(lp, XTE_MAW0_OFFSET, +					     multi_addr_msw); +			multi_addr_lsw = ((ha->addr[5] << 8) | +					  (ha->addr[4]) | (i << 16)); +			temac_indirect_out32(lp, XTE_MAW1_OFFSET, +					     multi_addr_lsw); +			i++; +		} +	} else { +		val = temac_indirect_in32(lp, XTE_AFM_OFFSET); +		temac_indirect_out32(lp, XTE_AFM_OFFSET, +				     val & ~XTE_AFM_EPPRM_MASK); +		temac_indirect_out32(lp, XTE_MAW0_OFFSET, 0); +		temac_indirect_out32(lp, XTE_MAW1_OFFSET, 0); +		dev_info(&ndev->dev, "Promiscuous mode disabled.\n"); +	} +	mutex_unlock(&lp->indirect_mutex); +} + +struct temac_option { +	int flg; +	u32 opt; +	u32 reg; +	u32 m_or; +	u32 m_and; +} temac_options[] = { +	/* Turn on jumbo packet support for both Rx and Tx */ +	{ +		.opt = XTE_OPTION_JUMBO, +		.reg = XTE_TXC_OFFSET, +		.m_or = XTE_TXC_TXJMBO_MASK, +	}, +	{ +		.opt = XTE_OPTION_JUMBO, +		.reg = XTE_RXC1_OFFSET, +		.m_or =XTE_RXC1_RXJMBO_MASK, +	}, +	/* Turn on VLAN packet support for both Rx and Tx */ +	{ +		.opt = XTE_OPTION_VLAN, +		.reg = XTE_TXC_OFFSET, +		.m_or =XTE_TXC_TXVLAN_MASK, +	}, +	{ +		.opt = XTE_OPTION_VLAN, +		.reg = XTE_RXC1_OFFSET, +		.m_or =XTE_RXC1_RXVLAN_MASK, +	}, +	/* Turn on FCS stripping on receive packets */ +	{ +		.opt = XTE_OPTION_FCS_STRIP, +		.reg = XTE_RXC1_OFFSET, +		.m_or =XTE_RXC1_RXFCS_MASK, +	}, +	/* Turn on FCS insertion on transmit packets */ +	{ +		.opt = XTE_OPTION_FCS_INSERT, +		.reg = XTE_TXC_OFFSET, +		.m_or =XTE_TXC_TXFCS_MASK, +	}, +	/* Turn on length/type field checking on receive packets */ +	{ +		.opt = XTE_OPTION_LENTYPE_ERR, +		.reg = XTE_RXC1_OFFSET, +		.m_or =XTE_RXC1_RXLT_MASK, +	}, +	/* Turn on flow control */ +	{ +		.opt = XTE_OPTION_FLOW_CONTROL, +		.reg = XTE_FCC_OFFSET, +		.m_or =XTE_FCC_RXFLO_MASK, +	}, +	/* Turn on flow control */ +	{ +		.opt = XTE_OPTION_FLOW_CONTROL, +		.reg = XTE_FCC_OFFSET, +		.m_or =XTE_FCC_TXFLO_MASK, +	}, +	/* Turn on promiscuous frame filtering (all frames are received ) */ +	{ +		.opt = XTE_OPTION_PROMISC, +		.reg = XTE_AFM_OFFSET, +		.m_or =XTE_AFM_EPPRM_MASK, +	}, +	/* Enable transmitter if not already enabled */ +	{ +		.opt = XTE_OPTION_TXEN, +		.reg = XTE_TXC_OFFSET, +		.m_or =XTE_TXC_TXEN_MASK, +	}, +	/* Enable receiver? */ +	{ +		.opt = XTE_OPTION_RXEN, +		.reg = XTE_RXC1_OFFSET, +		.m_or =XTE_RXC1_RXEN_MASK, +	}, +	{} +}; + +/** + * temac_setoptions + */ +static u32 temac_setoptions(struct net_device *ndev, u32 options) +{ +	struct temac_local *lp = netdev_priv(ndev); +	struct temac_option *tp = &temac_options[0]; +	int reg; + +	mutex_lock(&lp->indirect_mutex); +	while (tp->opt) { +		reg = temac_indirect_in32(lp, tp->reg) & ~tp->m_or; +		if (options & tp->opt) +			reg |= tp->m_or; +		temac_indirect_out32(lp, tp->reg, reg); +		tp++; +	} +	lp->options |= options; +	mutex_unlock(&lp->indirect_mutex); + +	return 0; +} + +/* Initialize temac */ +static void temac_device_reset(struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); +	u32 timeout; +	u32 val; + +	/* Perform a software reset */ + +	/* 0x300 host enable bit ? */ +	/* reset PHY through control register ?:1 */ + +	dev_dbg(&ndev->dev, "%s()\n", __func__); + +	mutex_lock(&lp->indirect_mutex); +	/* Reset the receiver and wait for it to finish reset */ +	temac_indirect_out32(lp, XTE_RXC1_OFFSET, XTE_RXC1_RXRST_MASK); +	timeout = 1000; +	while (temac_indirect_in32(lp, XTE_RXC1_OFFSET) & XTE_RXC1_RXRST_MASK) { +		udelay(1); +		if (--timeout == 0) { +			dev_err(&ndev->dev, +				"temac_device_reset RX reset timeout!!\n"); +			break; +		} +	} + +	/* Reset the transmitter and wait for it to finish reset */ +	temac_indirect_out32(lp, XTE_TXC_OFFSET, XTE_TXC_TXRST_MASK); +	timeout = 1000; +	while (temac_indirect_in32(lp, XTE_TXC_OFFSET) & XTE_TXC_TXRST_MASK) { +		udelay(1); +		if (--timeout == 0) { +			dev_err(&ndev->dev, +				"temac_device_reset TX reset timeout!!\n"); +			break; +		} +	} + +	/* Disable the receiver */ +	val = temac_indirect_in32(lp, XTE_RXC1_OFFSET); +	temac_indirect_out32(lp, XTE_RXC1_OFFSET, val & ~XTE_RXC1_RXEN_MASK); + +	/* Reset Local Link (DMA) */ +	lp->dma_out(lp, DMA_CONTROL_REG, DMA_CONTROL_RST); +	timeout = 1000; +	while (lp->dma_in(lp, DMA_CONTROL_REG) & DMA_CONTROL_RST) { +		udelay(1); +		if (--timeout == 0) { +			dev_err(&ndev->dev, +				"temac_device_reset DMA reset timeout!!\n"); +			break; +		} +	} +	lp->dma_out(lp, DMA_CONTROL_REG, DMA_TAIL_ENABLE); + +	if (temac_dma_bd_init(ndev)) { +		dev_err(&ndev->dev, +				"temac_device_reset descriptor allocation failed\n"); +	} + +	temac_indirect_out32(lp, XTE_RXC0_OFFSET, 0); +	temac_indirect_out32(lp, XTE_RXC1_OFFSET, 0); +	temac_indirect_out32(lp, XTE_TXC_OFFSET, 0); +	temac_indirect_out32(lp, XTE_FCC_OFFSET, XTE_FCC_RXFLO_MASK); + +	mutex_unlock(&lp->indirect_mutex); + +	/* Sync default options with HW +	 * but leave receiver and transmitter disabled.  */ +	temac_setoptions(ndev, +			 lp->options & ~(XTE_OPTION_TXEN | XTE_OPTION_RXEN)); + +	temac_do_set_mac_address(ndev); + +	/* Set address filter table */ +	temac_set_multicast_list(ndev); +	if (temac_setoptions(ndev, lp->options)) +		dev_err(&ndev->dev, "Error setting TEMAC options\n"); + +	/* Init Driver variable */ +	ndev->trans_start = jiffies; /* prevent tx timeout */ +} + +void temac_adjust_link(struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); +	struct phy_device *phy = lp->phy_dev; +	u32 mii_speed; +	int link_state; + +	/* hash together the state values to decide if something has changed */ +	link_state = phy->speed | (phy->duplex << 1) | phy->link; + +	mutex_lock(&lp->indirect_mutex); +	if (lp->last_link != link_state) { +		mii_speed = temac_indirect_in32(lp, XTE_EMCFG_OFFSET); +		mii_speed &= ~XTE_EMCFG_LINKSPD_MASK; + +		switch (phy->speed) { +		case SPEED_1000: mii_speed |= XTE_EMCFG_LINKSPD_1000; break; +		case SPEED_100: mii_speed |= XTE_EMCFG_LINKSPD_100; break; +		case SPEED_10: mii_speed |= XTE_EMCFG_LINKSPD_10; break; +		} + +		/* Write new speed setting out to TEMAC */ +		temac_indirect_out32(lp, XTE_EMCFG_OFFSET, mii_speed); +		lp->last_link = link_state; +		phy_print_status(phy); +	} +	mutex_unlock(&lp->indirect_mutex); +} + +static void temac_start_xmit_done(struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); +	struct cdmac_bd *cur_p; +	unsigned int stat = 0; + +	cur_p = &lp->tx_bd_v[lp->tx_bd_ci]; +	stat = cur_p->app0; + +	while (stat & STS_CTRL_APP0_CMPLT) { +		dma_unmap_single(ndev->dev.parent, cur_p->phys, cur_p->len, +				 DMA_TO_DEVICE); +		if (cur_p->app4) +			dev_kfree_skb_irq((struct sk_buff *)cur_p->app4); +		cur_p->app0 = 0; +		cur_p->app1 = 0; +		cur_p->app2 = 0; +		cur_p->app3 = 0; +		cur_p->app4 = 0; + +		ndev->stats.tx_packets++; +		ndev->stats.tx_bytes += cur_p->len; + +		lp->tx_bd_ci++; +		if (lp->tx_bd_ci >= TX_BD_NUM) +			lp->tx_bd_ci = 0; + +		cur_p = &lp->tx_bd_v[lp->tx_bd_ci]; +		stat = cur_p->app0; +	} + +	netif_wake_queue(ndev); +} + +static inline int temac_check_tx_bd_space(struct temac_local *lp, int num_frag) +{ +	struct cdmac_bd *cur_p; +	int tail; + +	tail = lp->tx_bd_tail; +	cur_p = &lp->tx_bd_v[tail]; + +	do { +		if (cur_p->app0) +			return NETDEV_TX_BUSY; + +		tail++; +		if (tail >= TX_BD_NUM) +			tail = 0; + +		cur_p = &lp->tx_bd_v[tail]; +		num_frag--; +	} while (num_frag >= 0); + +	return 0; +} + +static int temac_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); +	struct cdmac_bd *cur_p; +	dma_addr_t start_p, tail_p; +	int ii; +	unsigned long num_frag; +	skb_frag_t *frag; + +	num_frag = skb_shinfo(skb)->nr_frags; +	frag = &skb_shinfo(skb)->frags[0]; +	start_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail; +	cur_p = &lp->tx_bd_v[lp->tx_bd_tail]; + +	if (temac_check_tx_bd_space(lp, num_frag)) { +		if (!netif_queue_stopped(ndev)) { +			netif_stop_queue(ndev); +			return NETDEV_TX_BUSY; +		} +		return NETDEV_TX_BUSY; +	} + +	cur_p->app0 = 0; +	if (skb->ip_summed == CHECKSUM_PARTIAL) { +		unsigned int csum_start_off = skb_checksum_start_offset(skb); +		unsigned int csum_index_off = csum_start_off + skb->csum_offset; + +		cur_p->app0 |= 1; /* TX Checksum Enabled */ +		cur_p->app1 = (csum_start_off << 16) | csum_index_off; +		cur_p->app2 = 0;  /* initial checksum seed */ +	} + +	cur_p->app0 |= STS_CTRL_APP0_SOP; +	cur_p->len = skb_headlen(skb); +	cur_p->phys = dma_map_single(ndev->dev.parent, skb->data, skb->len, +				     DMA_TO_DEVICE); +	cur_p->app4 = (unsigned long)skb; + +	for (ii = 0; ii < num_frag; ii++) { +		lp->tx_bd_tail++; +		if (lp->tx_bd_tail >= TX_BD_NUM) +			lp->tx_bd_tail = 0; + +		cur_p = &lp->tx_bd_v[lp->tx_bd_tail]; +		cur_p->phys = dma_map_single(ndev->dev.parent, +					     skb_frag_address(frag), +					     skb_frag_size(frag), DMA_TO_DEVICE); +		cur_p->len = skb_frag_size(frag); +		cur_p->app0 = 0; +		frag++; +	} +	cur_p->app0 |= STS_CTRL_APP0_EOP; + +	tail_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail; +	lp->tx_bd_tail++; +	if (lp->tx_bd_tail >= TX_BD_NUM) +		lp->tx_bd_tail = 0; + +	skb_tx_timestamp(skb); + +	/* Kick off the transfer */ +	lp->dma_out(lp, TX_TAILDESC_PTR, tail_p); /* DMA start */ + +	return NETDEV_TX_OK; +} + + +static void ll_temac_recv(struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); +	struct sk_buff *skb, *new_skb; +	unsigned int bdstat; +	struct cdmac_bd *cur_p; +	dma_addr_t tail_p; +	int length; +	unsigned long flags; + +	spin_lock_irqsave(&lp->rx_lock, flags); + +	tail_p = lp->rx_bd_p + sizeof(*lp->rx_bd_v) * lp->rx_bd_ci; +	cur_p = &lp->rx_bd_v[lp->rx_bd_ci]; + +	bdstat = cur_p->app0; +	while ((bdstat & STS_CTRL_APP0_CMPLT)) { + +		skb = lp->rx_skb[lp->rx_bd_ci]; +		length = cur_p->app4 & 0x3FFF; + +		dma_unmap_single(ndev->dev.parent, cur_p->phys, length, +				 DMA_FROM_DEVICE); + +		skb_put(skb, length); +		skb->protocol = eth_type_trans(skb, ndev); +		skb_checksum_none_assert(skb); + +		/* if we're doing rx csum offload, set it up */ +		if (((lp->temac_features & TEMAC_FEATURE_RX_CSUM) != 0) && +		    (skb->protocol == htons(ETH_P_IP)) && +		    (skb->len > 64)) { + +			skb->csum = cur_p->app3 & 0xFFFF; +			skb->ip_summed = CHECKSUM_COMPLETE; +		} + +		if (!skb_defer_rx_timestamp(skb)) +			netif_rx(skb); + +		ndev->stats.rx_packets++; +		ndev->stats.rx_bytes += length; + +		new_skb = netdev_alloc_skb_ip_align(ndev, +						XTE_MAX_JUMBO_FRAME_SIZE); +		if (!new_skb) { +			spin_unlock_irqrestore(&lp->rx_lock, flags); +			return; +		} + +		cur_p->app0 = STS_CTRL_APP0_IRQONEND; +		cur_p->phys = dma_map_single(ndev->dev.parent, new_skb->data, +					     XTE_MAX_JUMBO_FRAME_SIZE, +					     DMA_FROM_DEVICE); +		cur_p->len = XTE_MAX_JUMBO_FRAME_SIZE; +		lp->rx_skb[lp->rx_bd_ci] = new_skb; + +		lp->rx_bd_ci++; +		if (lp->rx_bd_ci >= RX_BD_NUM) +			lp->rx_bd_ci = 0; + +		cur_p = &lp->rx_bd_v[lp->rx_bd_ci]; +		bdstat = cur_p->app0; +	} +	lp->dma_out(lp, RX_TAILDESC_PTR, tail_p); + +	spin_unlock_irqrestore(&lp->rx_lock, flags); +} + +static irqreturn_t ll_temac_tx_irq(int irq, void *_ndev) +{ +	struct net_device *ndev = _ndev; +	struct temac_local *lp = netdev_priv(ndev); +	unsigned int status; + +	status = lp->dma_in(lp, TX_IRQ_REG); +	lp->dma_out(lp, TX_IRQ_REG, status); + +	if (status & (IRQ_COAL | IRQ_DLY)) +		temac_start_xmit_done(lp->ndev); +	if (status & 0x080) +		dev_err(&ndev->dev, "DMA error 0x%x\n", status); + +	return IRQ_HANDLED; +} + +static irqreturn_t ll_temac_rx_irq(int irq, void *_ndev) +{ +	struct net_device *ndev = _ndev; +	struct temac_local *lp = netdev_priv(ndev); +	unsigned int status; + +	/* Read and clear the status registers */ +	status = lp->dma_in(lp, RX_IRQ_REG); +	lp->dma_out(lp, RX_IRQ_REG, status); + +	if (status & (IRQ_COAL | IRQ_DLY)) +		ll_temac_recv(lp->ndev); + +	return IRQ_HANDLED; +} + +static int temac_open(struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); +	int rc; + +	dev_dbg(&ndev->dev, "temac_open()\n"); + +	if (lp->phy_node) { +		lp->phy_dev = of_phy_connect(lp->ndev, lp->phy_node, +					     temac_adjust_link, 0, 0); +		if (!lp->phy_dev) { +			dev_err(lp->dev, "of_phy_connect() failed\n"); +			return -ENODEV; +		} + +		phy_start(lp->phy_dev); +	} + +	temac_device_reset(ndev); + +	rc = request_irq(lp->tx_irq, ll_temac_tx_irq, 0, ndev->name, ndev); +	if (rc) +		goto err_tx_irq; +	rc = request_irq(lp->rx_irq, ll_temac_rx_irq, 0, ndev->name, ndev); +	if (rc) +		goto err_rx_irq; + +	return 0; + + err_rx_irq: +	free_irq(lp->tx_irq, ndev); + err_tx_irq: +	if (lp->phy_dev) +		phy_disconnect(lp->phy_dev); +	lp->phy_dev = NULL; +	dev_err(lp->dev, "request_irq() failed\n"); +	return rc; +} + +static int temac_stop(struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); + +	dev_dbg(&ndev->dev, "temac_close()\n"); + +	free_irq(lp->tx_irq, ndev); +	free_irq(lp->rx_irq, ndev); + +	if (lp->phy_dev) +		phy_disconnect(lp->phy_dev); +	lp->phy_dev = NULL; + +	temac_dma_bd_release(ndev); + +	return 0; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void +temac_poll_controller(struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); + +	disable_irq(lp->tx_irq); +	disable_irq(lp->rx_irq); + +	ll_temac_rx_irq(lp->tx_irq, ndev); +	ll_temac_tx_irq(lp->rx_irq, ndev); + +	enable_irq(lp->tx_irq); +	enable_irq(lp->rx_irq); +} +#endif + +static int temac_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd) +{ +	struct temac_local *lp = netdev_priv(ndev); + +	if (!netif_running(ndev)) +		return -EINVAL; + +	if (!lp->phy_dev) +		return -EINVAL; + +	return phy_mii_ioctl(lp->phy_dev, rq, cmd); +} + +static const struct net_device_ops temac_netdev_ops = { +	.ndo_open = temac_open, +	.ndo_stop = temac_stop, +	.ndo_start_xmit = temac_start_xmit, +	.ndo_set_mac_address = temac_set_mac_address, +	.ndo_validate_addr = eth_validate_addr, +	.ndo_do_ioctl = temac_ioctl, +#ifdef CONFIG_NET_POLL_CONTROLLER +	.ndo_poll_controller = temac_poll_controller, +#endif +}; + +/* --------------------------------------------------------------------- + * SYSFS device attributes + */ +static ssize_t temac_show_llink_regs(struct device *dev, +				     struct device_attribute *attr, char *buf) +{ +	struct net_device *ndev = dev_get_drvdata(dev); +	struct temac_local *lp = netdev_priv(ndev); +	int i, len = 0; + +	for (i = 0; i < 0x11; i++) +		len += sprintf(buf + len, "%.8x%s", lp->dma_in(lp, i), +			       (i % 8) == 7 ? "\n" : " "); +	len += sprintf(buf + len, "\n"); + +	return len; +} + +static DEVICE_ATTR(llink_regs, 0440, temac_show_llink_regs, NULL); + +static struct attribute *temac_device_attrs[] = { +	&dev_attr_llink_regs.attr, +	NULL, +}; + +static const struct attribute_group temac_attr_group = { +	.attrs = temac_device_attrs, +}; + +/* ethtool support */ +static int temac_get_settings(struct net_device *ndev, struct ethtool_cmd *cmd) +{ +	struct temac_local *lp = netdev_priv(ndev); +	return phy_ethtool_gset(lp->phy_dev, cmd); +} + +static int temac_set_settings(struct net_device *ndev, struct ethtool_cmd *cmd) +{ +	struct temac_local *lp = netdev_priv(ndev); +	return phy_ethtool_sset(lp->phy_dev, cmd); +} + +static int temac_nway_reset(struct net_device *ndev) +{ +	struct temac_local *lp = netdev_priv(ndev); +	return phy_start_aneg(lp->phy_dev); +} + +static const struct ethtool_ops temac_ethtool_ops = { +	.get_settings = temac_get_settings, +	.set_settings = temac_set_settings, +	.nway_reset = temac_nway_reset, +	.get_link = ethtool_op_get_link, +	.get_ts_info = ethtool_op_get_ts_info, +}; + +static int temac_of_probe(struct platform_device *op) +{ +	struct device_node *np; +	struct temac_local *lp; +	struct net_device *ndev; +	const void *addr; +	__be32 *p; +	int size, rc = 0; + +	/* Init network device structure */ +	ndev = alloc_etherdev(sizeof(*lp)); +	if (!ndev) +		return -ENOMEM; + +	ether_setup(ndev); +	platform_set_drvdata(op, ndev); +	SET_NETDEV_DEV(ndev, &op->dev); +	ndev->flags &= ~IFF_MULTICAST;  /* clear multicast */ +	ndev->features = NETIF_F_SG; +	ndev->netdev_ops = &temac_netdev_ops; +	ndev->ethtool_ops = &temac_ethtool_ops; +#if 0 +	ndev->features |= NETIF_F_IP_CSUM; /* Can checksum TCP/UDP over IPv4. */ +	ndev->features |= NETIF_F_HW_CSUM; /* Can checksum all the packets. */ +	ndev->features |= NETIF_F_IPV6_CSUM; /* Can checksum IPV6 TCP/UDP */ +	ndev->features |= NETIF_F_HIGHDMA; /* Can DMA to high memory. */ +	ndev->features |= NETIF_F_HW_VLAN_CTAG_TX; /* Transmit VLAN hw accel */ +	ndev->features |= NETIF_F_HW_VLAN_CTAG_RX; /* Receive VLAN hw acceleration */ +	ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER; /* Receive VLAN filtering */ +	ndev->features |= NETIF_F_VLAN_CHALLENGED; /* cannot handle VLAN pkts */ +	ndev->features |= NETIF_F_GSO; /* Enable software GSO. */ +	ndev->features |= NETIF_F_MULTI_QUEUE; /* Has multiple TX/RX queues */ +	ndev->features |= NETIF_F_LRO; /* large receive offload */ +#endif + +	/* setup temac private info structure */ +	lp = netdev_priv(ndev); +	lp->ndev = ndev; +	lp->dev = &op->dev; +	lp->options = XTE_OPTION_DEFAULTS; +	spin_lock_init(&lp->rx_lock); +	mutex_init(&lp->indirect_mutex); + +	/* map device registers */ +	lp->regs = of_iomap(op->dev.of_node, 0); +	if (!lp->regs) { +		dev_err(&op->dev, "could not map temac regs.\n"); +		goto nodev; +	} + +	/* Setup checksum offload, but default to off if not specified */ +	lp->temac_features = 0; +	p = (__be32 *)of_get_property(op->dev.of_node, "xlnx,txcsum", NULL); +	if (p && be32_to_cpu(*p)) { +		lp->temac_features |= TEMAC_FEATURE_TX_CSUM; +		/* Can checksum TCP/UDP over IPv4. */ +		ndev->features |= NETIF_F_IP_CSUM; +	} +	p = (__be32 *)of_get_property(op->dev.of_node, "xlnx,rxcsum", NULL); +	if (p && be32_to_cpu(*p)) +		lp->temac_features |= TEMAC_FEATURE_RX_CSUM; + +	/* Find the DMA node, map the DMA registers, and decode the DMA IRQs */ +	np = of_parse_phandle(op->dev.of_node, "llink-connected", 0); +	if (!np) { +		dev_err(&op->dev, "could not find DMA node\n"); +		goto err_iounmap; +	} + +	/* Setup the DMA register accesses, could be DCR or memory mapped */ +	if (temac_dcr_setup(lp, op, np)) { + +		/* no DCR in the device tree, try non-DCR */ +		lp->sdma_regs = of_iomap(np, 0); +		if (lp->sdma_regs) { +			lp->dma_in = temac_dma_in32; +			lp->dma_out = temac_dma_out32; +			dev_dbg(&op->dev, "MEM base: %p\n", lp->sdma_regs); +		} else { +			dev_err(&op->dev, "unable to map DMA registers\n"); +			of_node_put(np); +			goto err_iounmap; +		} +	} + +	lp->rx_irq = irq_of_parse_and_map(np, 0); +	lp->tx_irq = irq_of_parse_and_map(np, 1); + +	of_node_put(np); /* Finished with the DMA node; drop the reference */ + +	if (!lp->rx_irq || !lp->tx_irq) { +		dev_err(&op->dev, "could not determine irqs\n"); +		rc = -ENOMEM; +		goto err_iounmap_2; +	} + + +	/* Retrieve the MAC address */ +	addr = of_get_property(op->dev.of_node, "local-mac-address", &size); +	if ((!addr) || (size != 6)) { +		dev_err(&op->dev, "could not find MAC address\n"); +		rc = -ENODEV; +		goto err_iounmap_2; +	} +	temac_init_mac_address(ndev, (void *)addr); + +	rc = temac_mdio_setup(lp, op->dev.of_node); +	if (rc) +		dev_warn(&op->dev, "error registering MDIO bus\n"); + +	lp->phy_node = of_parse_phandle(op->dev.of_node, "phy-handle", 0); +	if (lp->phy_node) +		dev_dbg(lp->dev, "using PHY node %s (%p)\n", np->full_name, np); + +	/* Add the device attributes */ +	rc = sysfs_create_group(&lp->dev->kobj, &temac_attr_group); +	if (rc) { +		dev_err(lp->dev, "Error creating sysfs files\n"); +		goto err_iounmap_2; +	} + +	rc = register_netdev(lp->ndev); +	if (rc) { +		dev_err(lp->dev, "register_netdev() error (%i)\n", rc); +		goto err_register_ndev; +	} + +	return 0; + + err_register_ndev: +	sysfs_remove_group(&lp->dev->kobj, &temac_attr_group); + err_iounmap_2: +	if (lp->sdma_regs) +		iounmap(lp->sdma_regs); + err_iounmap: +	iounmap(lp->regs); + nodev: +	free_netdev(ndev); +	ndev = NULL; +	return rc; +} + +static int temac_of_remove(struct platform_device *op) +{ +	struct net_device *ndev = platform_get_drvdata(op); +	struct temac_local *lp = netdev_priv(ndev); + +	temac_mdio_teardown(lp); +	unregister_netdev(ndev); +	sysfs_remove_group(&lp->dev->kobj, &temac_attr_group); +	if (lp->phy_node) +		of_node_put(lp->phy_node); +	lp->phy_node = NULL; +	iounmap(lp->regs); +	if (lp->sdma_regs) +		iounmap(lp->sdma_regs); +	free_netdev(ndev); +	return 0; +} + +static struct of_device_id temac_of_match[] = { +	{ .compatible = "xlnx,xps-ll-temac-1.01.b", }, +	{ .compatible = "xlnx,xps-ll-temac-2.00.a", }, +	{ .compatible = "xlnx,xps-ll-temac-2.02.a", }, +	{ .compatible = "xlnx,xps-ll-temac-2.03.a", }, +	{}, +}; +MODULE_DEVICE_TABLE(of, temac_of_match); + +static struct platform_driver temac_of_driver = { +	.probe = temac_of_probe, +	.remove = temac_of_remove, +	.driver = { +		.owner = THIS_MODULE, +		.name = "xilinx_temac", +		.of_match_table = temac_of_match, +	}, +}; + +module_platform_driver(temac_of_driver); + +MODULE_DESCRIPTION("Xilinx LL_TEMAC Ethernet driver"); +MODULE_AUTHOR("Yoshio Kashiwagi"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/xilinx/ll_temac_mdio.c b/drivers/net/ethernet/xilinx/ll_temac_mdio.c new file mode 100644 index 00000000000..8cf9d4f56bb --- /dev/null +++ b/drivers/net/ethernet/xilinx/ll_temac_mdio.c @@ -0,0 +1,122 @@ +/* + * MDIO bus driver for the Xilinx TEMAC device + * + * Copyright (c) 2009 Secret Lab Technologies, Ltd. + */ + +#include <linux/io.h> +#include <linux/netdevice.h> +#include <linux/mutex.h> +#include <linux/phy.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/of_mdio.h> + +#include "ll_temac.h" + +/* --------------------------------------------------------------------- + * MDIO Bus functions + */ +static int temac_mdio_read(struct mii_bus *bus, int phy_id, int reg) +{ +	struct temac_local *lp = bus->priv; +	u32 rc; + +	/* Write the PHY address to the MIIM Access Initiator register. +	 * When the transfer completes, the PHY register value will appear +	 * in the LSW0 register */ +	mutex_lock(&lp->indirect_mutex); +	temac_iow(lp, XTE_LSW0_OFFSET, (phy_id << 5) | reg); +	rc = temac_indirect_in32(lp, XTE_MIIMAI_OFFSET); +	mutex_unlock(&lp->indirect_mutex); + +	dev_dbg(lp->dev, "temac_mdio_read(phy_id=%i, reg=%x) == %x\n", +		phy_id, reg, rc); + +	return rc; +} + +static int temac_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val) +{ +	struct temac_local *lp = bus->priv; + +	dev_dbg(lp->dev, "temac_mdio_write(phy_id=%i, reg=%x, val=%x)\n", +		phy_id, reg, val); + +	/* First write the desired value into the write data register +	 * and then write the address into the access initiator register +	 */ +	mutex_lock(&lp->indirect_mutex); +	temac_indirect_out32(lp, XTE_MGTDR_OFFSET, val); +	temac_indirect_out32(lp, XTE_MIIMAI_OFFSET, (phy_id << 5) | reg); +	mutex_unlock(&lp->indirect_mutex); + +	return 0; +} + +int temac_mdio_setup(struct temac_local *lp, struct device_node *np) +{ +	struct mii_bus *bus; +	const u32 *bus_hz; +	int clk_div; +	int rc, size; +	struct resource res; + +	/* Calculate a reasonable divisor for the clock rate */ +	clk_div = 0x3f; /* worst-case default setting */ +	bus_hz = of_get_property(np, "clock-frequency", &size); +	if (bus_hz && size >= sizeof(*bus_hz)) { +		clk_div = (*bus_hz) / (2500 * 1000 * 2) - 1; +		if (clk_div < 1) +			clk_div = 1; +		if (clk_div > 0x3f) +			clk_div = 0x3f; +	} + +	/* Enable the MDIO bus by asserting the enable bit and writing +	 * in the clock config */ +	mutex_lock(&lp->indirect_mutex); +	temac_indirect_out32(lp, XTE_MC_OFFSET, 1 << 6 | clk_div); +	mutex_unlock(&lp->indirect_mutex); + +	bus = mdiobus_alloc(); +	if (!bus) +		return -ENOMEM; + +	of_address_to_resource(np, 0, &res); +	snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx", +		 (unsigned long long)res.start); +	bus->priv = lp; +	bus->name = "Xilinx TEMAC MDIO"; +	bus->read = temac_mdio_read; +	bus->write = temac_mdio_write; +	bus->parent = lp->dev; +	bus->irq = lp->mdio_irqs; /* preallocated IRQ table */ + +	lp->mii_bus = bus; + +	rc = of_mdiobus_register(bus, np); +	if (rc) +		goto err_register; + +	mutex_lock(&lp->indirect_mutex); +	dev_dbg(lp->dev, "MDIO bus registered;  MC:%x\n", +		temac_indirect_in32(lp, XTE_MC_OFFSET)); +	mutex_unlock(&lp->indirect_mutex); +	return 0; + + err_register: +	mdiobus_free(bus); +	return rc; +} + +void temac_mdio_teardown(struct temac_local *lp) +{ +	mdiobus_unregister(lp->mii_bus); +	kfree(lp->mii_bus->irq); +	mdiobus_free(lp->mii_bus); +	lp->mii_bus = NULL; +} + diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h new file mode 100644 index 00000000000..44b8d2bad8c --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -0,0 +1,506 @@ +/* + * Definitions for Xilinx Axi Ethernet device driver. + * + * Copyright (c) 2009 Secret Lab Technologies, Ltd. + * Copyright (c) 2010 - 2012 Xilinx, Inc. All rights reserved. + */ + +#ifndef XILINX_AXIENET_H +#define XILINX_AXIENET_H + +#include <linux/netdevice.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> + +/* Packet size info */ +#define XAE_HDR_SIZE			14 /* Size of Ethernet header */ +#define XAE_HDR_VLAN_SIZE		18 /* Size of an Ethernet hdr + VLAN */ +#define XAE_TRL_SIZE			 4 /* Size of Ethernet trailer (FCS) */ +#define XAE_MTU			      1500 /* Max MTU of an Ethernet frame */ +#define XAE_JUMBO_MTU		      9000 /* Max MTU of a jumbo Eth. frame */ + +#define XAE_MAX_FRAME_SIZE	 (XAE_MTU + XAE_HDR_SIZE + XAE_TRL_SIZE) +#define XAE_MAX_VLAN_FRAME_SIZE  (XAE_MTU + XAE_HDR_VLAN_SIZE + XAE_TRL_SIZE) +#define XAE_MAX_JUMBO_FRAME_SIZE (XAE_JUMBO_MTU + XAE_HDR_SIZE + XAE_TRL_SIZE) + +/* Configuration options */ + +/* Accept all incoming packets. Default: disabled (cleared) */ +#define XAE_OPTION_PROMISC			(1 << 0) + +/* Jumbo frame support for Tx & Rx. Default: disabled (cleared) */ +#define XAE_OPTION_JUMBO			(1 << 1) + +/* VLAN Rx & Tx frame support. Default: disabled (cleared) */ +#define XAE_OPTION_VLAN				(1 << 2) + +/* Enable recognition of flow control frames on Rx. Default: enabled (set) */ +#define XAE_OPTION_FLOW_CONTROL			(1 << 4) + +/* Strip FCS and PAD from incoming frames. Note: PAD from VLAN frames is not + * stripped. Default: disabled (set) */ +#define XAE_OPTION_FCS_STRIP			(1 << 5) + +/* Generate FCS field and add PAD automatically for outgoing frames. + * Default: enabled (set) */ +#define XAE_OPTION_FCS_INSERT			(1 << 6) + +/* Enable Length/Type error checking for incoming frames. When this option is + * set, the MAC will filter frames that have a mismatched type/length field + * and if XAE_OPTION_REPORT_RXERR is set, the user is notified when these + * types of frames are encountered. When this option is cleared, the MAC will + * allow these types of frames to be received. Default: enabled (set) */ +#define XAE_OPTION_LENTYPE_ERR			(1 << 7) + +/* Enable the transmitter. Default: enabled (set) */ +#define XAE_OPTION_TXEN				(1 << 11) + +/*  Enable the receiver. Default: enabled (set) */ +#define XAE_OPTION_RXEN				(1 << 12) + +/*  Default options set when device is initialized or reset */ +#define XAE_OPTION_DEFAULTS				   \ +				(XAE_OPTION_TXEN |	   \ +				 XAE_OPTION_FLOW_CONTROL | \ +				 XAE_OPTION_RXEN) + +/* Axi DMA Register definitions */ + +#define XAXIDMA_TX_CR_OFFSET	0x00000000 /* Channel control */ +#define XAXIDMA_TX_SR_OFFSET	0x00000004 /* Status */ +#define XAXIDMA_TX_CDESC_OFFSET	0x00000008 /* Current descriptor pointer */ +#define XAXIDMA_TX_TDESC_OFFSET	0x00000010 /* Tail descriptor pointer */ + +#define XAXIDMA_RX_CR_OFFSET	0x00000030 /* Channel control */ +#define XAXIDMA_RX_SR_OFFSET	0x00000034 /* Status */ +#define XAXIDMA_RX_CDESC_OFFSET	0x00000038 /* Current descriptor pointer */ +#define XAXIDMA_RX_TDESC_OFFSET	0x00000040 /* Tail descriptor pointer */ + +#define XAXIDMA_CR_RUNSTOP_MASK	0x00000001 /* Start/stop DMA channel */ +#define XAXIDMA_CR_RESET_MASK	0x00000004 /* Reset DMA engine */ + +#define XAXIDMA_BD_NDESC_OFFSET		0x00 /* Next descriptor pointer */ +#define XAXIDMA_BD_BUFA_OFFSET		0x08 /* Buffer address */ +#define XAXIDMA_BD_CTRL_LEN_OFFSET	0x18 /* Control/buffer length */ +#define XAXIDMA_BD_STS_OFFSET		0x1C /* Status */ +#define XAXIDMA_BD_USR0_OFFSET		0x20 /* User IP specific word0 */ +#define XAXIDMA_BD_USR1_OFFSET		0x24 /* User IP specific word1 */ +#define XAXIDMA_BD_USR2_OFFSET		0x28 /* User IP specific word2 */ +#define XAXIDMA_BD_USR3_OFFSET		0x2C /* User IP specific word3 */ +#define XAXIDMA_BD_USR4_OFFSET		0x30 /* User IP specific word4 */ +#define XAXIDMA_BD_ID_OFFSET		0x34 /* Sw ID */ +#define XAXIDMA_BD_HAS_STSCNTRL_OFFSET	0x38 /* Whether has stscntrl strm */ +#define XAXIDMA_BD_HAS_DRE_OFFSET	0x3C /* Whether has DRE */ + +#define XAXIDMA_BD_HAS_DRE_SHIFT	8 /* Whether has DRE shift */ +#define XAXIDMA_BD_HAS_DRE_MASK		0xF00 /* Whether has DRE mask */ +#define XAXIDMA_BD_WORDLEN_MASK		0xFF /* Whether has DRE mask */ + +#define XAXIDMA_BD_CTRL_LENGTH_MASK	0x007FFFFF /* Requested len */ +#define XAXIDMA_BD_CTRL_TXSOF_MASK	0x08000000 /* First tx packet */ +#define XAXIDMA_BD_CTRL_TXEOF_MASK	0x04000000 /* Last tx packet */ +#define XAXIDMA_BD_CTRL_ALL_MASK	0x0C000000 /* All control bits */ + +#define XAXIDMA_DELAY_MASK		0xFF000000 /* Delay timeout counter */ +#define XAXIDMA_COALESCE_MASK		0x00FF0000 /* Coalesce counter */ + +#define XAXIDMA_DELAY_SHIFT		24 +#define XAXIDMA_COALESCE_SHIFT		16 + +#define XAXIDMA_IRQ_IOC_MASK		0x00001000 /* Completion intr */ +#define XAXIDMA_IRQ_DELAY_MASK		0x00002000 /* Delay interrupt */ +#define XAXIDMA_IRQ_ERROR_MASK		0x00004000 /* Error interrupt */ +#define XAXIDMA_IRQ_ALL_MASK		0x00007000 /* All interrupts */ + +/* Default TX/RX Threshold and waitbound values for SGDMA mode */ +#define XAXIDMA_DFT_TX_THRESHOLD	24 +#define XAXIDMA_DFT_TX_WAITBOUND	254 +#define XAXIDMA_DFT_RX_THRESHOLD	24 +#define XAXIDMA_DFT_RX_WAITBOUND	254 + +#define XAXIDMA_BD_CTRL_TXSOF_MASK	0x08000000 /* First tx packet */ +#define XAXIDMA_BD_CTRL_TXEOF_MASK	0x04000000 /* Last tx packet */ +#define XAXIDMA_BD_CTRL_ALL_MASK	0x0C000000 /* All control bits */ + +#define XAXIDMA_BD_STS_ACTUAL_LEN_MASK	0x007FFFFF /* Actual len */ +#define XAXIDMA_BD_STS_COMPLETE_MASK	0x80000000 /* Completed */ +#define XAXIDMA_BD_STS_DEC_ERR_MASK	0x40000000 /* Decode error */ +#define XAXIDMA_BD_STS_SLV_ERR_MASK	0x20000000 /* Slave error */ +#define XAXIDMA_BD_STS_INT_ERR_MASK	0x10000000 /* Internal err */ +#define XAXIDMA_BD_STS_ALL_ERR_MASK	0x70000000 /* All errors */ +#define XAXIDMA_BD_STS_RXSOF_MASK	0x08000000 /* First rx pkt */ +#define XAXIDMA_BD_STS_RXEOF_MASK	0x04000000 /* Last rx pkt */ +#define XAXIDMA_BD_STS_ALL_MASK		0xFC000000 /* All status bits */ + +#define XAXIDMA_BD_MINIMUM_ALIGNMENT	0x40 + +/* Axi Ethernet registers definition */ +#define XAE_RAF_OFFSET		0x00000000 /* Reset and Address filter */ +#define XAE_TPF_OFFSET		0x00000004 /* Tx Pause Frame */ +#define XAE_IFGP_OFFSET		0x00000008 /* Tx Inter-frame gap adjustment*/ +#define XAE_IS_OFFSET		0x0000000C /* Interrupt status */ +#define XAE_IP_OFFSET		0x00000010 /* Interrupt pending */ +#define XAE_IE_OFFSET		0x00000014 /* Interrupt enable */ +#define XAE_TTAG_OFFSET		0x00000018 /* Tx VLAN TAG */ +#define XAE_RTAG_OFFSET		0x0000001C /* Rx VLAN TAG */ +#define XAE_UAWL_OFFSET		0x00000020 /* Unicast address word lower */ +#define XAE_UAWU_OFFSET		0x00000024 /* Unicast address word upper */ +#define XAE_TPID0_OFFSET	0x00000028 /* VLAN TPID0 register */ +#define XAE_TPID1_OFFSET	0x0000002C /* VLAN TPID1 register */ +#define XAE_PPST_OFFSET		0x00000030 /* PCS PMA Soft Temac Status Reg */ +#define XAE_RCW0_OFFSET		0x00000400 /* Rx Configuration Word 0 */ +#define XAE_RCW1_OFFSET		0x00000404 /* Rx Configuration Word 1 */ +#define XAE_TC_OFFSET		0x00000408 /* Tx Configuration */ +#define XAE_FCC_OFFSET		0x0000040C /* Flow Control Configuration */ +#define XAE_EMMC_OFFSET		0x00000410 /* EMAC mode configuration */ +#define XAE_PHYC_OFFSET		0x00000414 /* RGMII/SGMII configuration */ +#define XAE_MDIO_MC_OFFSET	0x00000500 /* MII Management Config */ +#define XAE_MDIO_MCR_OFFSET	0x00000504 /* MII Management Control */ +#define XAE_MDIO_MWD_OFFSET	0x00000508 /* MII Management Write Data */ +#define XAE_MDIO_MRD_OFFSET	0x0000050C /* MII Management Read Data */ +#define XAE_MDIO_MIS_OFFSET	0x00000600 /* MII Management Interrupt Status */ +#define XAE_MDIO_MIP_OFFSET	0x00000620 /* MII Mgmt Interrupt Pending +					    * register offset */ +#define XAE_MDIO_MIE_OFFSET	0x00000640 /* MII Management Interrupt Enable +					    * register offset */ +#define XAE_MDIO_MIC_OFFSET	0x00000660 /* MII Management Interrupt Clear +					    * register offset. */ +#define XAE_UAW0_OFFSET		0x00000700 /* Unicast address word 0 */ +#define XAE_UAW1_OFFSET		0x00000704 /* Unicast address word 1 */ +#define XAE_FMI_OFFSET		0x00000708 /* Filter Mask Index */ +#define XAE_AF0_OFFSET		0x00000710 /* Address Filter 0 */ +#define XAE_AF1_OFFSET		0x00000714 /* Address Filter 1 */ + +#define XAE_TX_VLAN_DATA_OFFSET 0x00004000 /* TX VLAN data table address */ +#define XAE_RX_VLAN_DATA_OFFSET 0x00008000 /* RX VLAN data table address */ +#define XAE_MCAST_TABLE_OFFSET	0x00020000 /* Multicast table address */ + +/* Bit Masks for Axi Ethernet RAF register */ +#define XAE_RAF_MCSTREJ_MASK		0x00000002 /* Reject receive multicast +						    * destination address */ +#define XAE_RAF_BCSTREJ_MASK		0x00000004 /* Reject receive broadcast +						    * destination address */ +#define XAE_RAF_TXVTAGMODE_MASK		0x00000018 /* Tx VLAN TAG mode */ +#define XAE_RAF_RXVTAGMODE_MASK		0x00000060 /* Rx VLAN TAG mode */ +#define XAE_RAF_TXVSTRPMODE_MASK	0x00000180 /* Tx VLAN STRIP mode */ +#define XAE_RAF_RXVSTRPMODE_MASK	0x00000600 /* Rx VLAN STRIP mode */ +#define XAE_RAF_NEWFNCENBL_MASK		0x00000800 /* New function mode */ +#define XAE_RAF_EMULTIFLTRENBL_MASK	0x00001000 /* Exteneded Multicast +						    * Filtering mode +						    */ +#define XAE_RAF_STATSRST_MASK		0x00002000 /* Stats. Counter Reset */ +#define XAE_RAF_RXBADFRMEN_MASK		0x00004000 /* Recv Bad Frame Enable */ +#define XAE_RAF_TXVTAGMODE_SHIFT	3 /* Tx Tag mode shift bits */ +#define XAE_RAF_RXVTAGMODE_SHIFT	5 /* Rx Tag mode shift bits */ +#define XAE_RAF_TXVSTRPMODE_SHIFT	7 /* Tx strip mode shift bits*/ +#define XAE_RAF_RXVSTRPMODE_SHIFT	9 /* Rx Strip mode shift bits*/ + +/* Bit Masks for Axi Ethernet TPF and IFGP registers */ +#define XAE_TPF_TPFV_MASK		0x0000FFFF /* Tx pause frame value */ +#define XAE_IFGP0_IFGP_MASK		0x0000007F /* Transmit inter-frame +						    * gap adjustment value */ + +/* Bit Masks for Axi Ethernet IS, IE and IP registers, Same masks apply + * for all 3 registers. */ +#define XAE_INT_HARDACSCMPLT_MASK	0x00000001 /* Hard register access +						    * complete */ +#define XAE_INT_AUTONEG_MASK		0x00000002 /* Auto negotiation +						    * complete */ +#define XAE_INT_RXCMPIT_MASK		0x00000004 /* Rx complete */ +#define XAE_INT_RXRJECT_MASK		0x00000008 /* Rx frame rejected */ +#define XAE_INT_RXFIFOOVR_MASK		0x00000010 /* Rx fifo overrun */ +#define XAE_INT_TXCMPIT_MASK		0x00000020 /* Tx complete */ +#define XAE_INT_RXDCMLOCK_MASK		0x00000040 /* Rx Dcm Lock */ +#define XAE_INT_MGTRDY_MASK		0x00000080 /* MGT clock Lock */ +#define XAE_INT_PHYRSTCMPLT_MASK	0x00000100 /* Phy Reset complete */ +#define XAE_INT_ALL_MASK		0x0000003F /* All the ints */ + +#define XAE_INT_RECV_ERROR_MASK				\ +	(XAE_INT_RXRJECT_MASK | XAE_INT_RXFIFOOVR_MASK) /* INT bits that +							 * indicate receive +							 * errors */ + +/* Bit masks for Axi Ethernet VLAN TPID Word 0 register */ +#define XAE_TPID_0_MASK		0x0000FFFF /* TPID 0 */ +#define XAE_TPID_1_MASK		0xFFFF0000 /* TPID 1 */ + +/* Bit masks for Axi Ethernet VLAN TPID Word 1 register */ +#define XAE_TPID_2_MASK		0x0000FFFF /* TPID 0 */ +#define XAE_TPID_3_MASK		0xFFFF0000 /* TPID 1 */ + +/* Bit masks for Axi Ethernet RCW1 register */ +#define XAE_RCW1_RST_MASK	0x80000000 /* Reset */ +#define XAE_RCW1_JUM_MASK	0x40000000 /* Jumbo frame enable */ +#define XAE_RCW1_FCS_MASK	0x20000000 /* In-Band FCS enable +					    * (FCS not stripped) */ +#define XAE_RCW1_RX_MASK	0x10000000 /* Receiver enable */ +#define XAE_RCW1_VLAN_MASK	0x08000000 /* VLAN frame enable */ +#define XAE_RCW1_LT_DIS_MASK	0x02000000 /* Length/type field valid check +					    * disable */ +#define XAE_RCW1_CL_DIS_MASK	0x01000000 /* Control frame Length check +					    * disable */ +#define XAE_RCW1_PAUSEADDR_MASK 0x0000FFFF /* Pause frame source address +					    * bits [47:32]. Bits [31:0] are +					    * stored in register RCW0 */ + +/* Bit masks for Axi Ethernet TC register */ +#define XAE_TC_RST_MASK		0x80000000 /* Reset */ +#define XAE_TC_JUM_MASK		0x40000000 /* Jumbo frame enable */ +#define XAE_TC_FCS_MASK		0x20000000 /* In-Band FCS enable +					    * (FCS not generated) */ +#define XAE_TC_TX_MASK		0x10000000 /* Transmitter enable */ +#define XAE_TC_VLAN_MASK	0x08000000 /* VLAN frame enable */ +#define XAE_TC_IFG_MASK		0x02000000 /* Inter-frame gap adjustment +					    * enable */ + +/* Bit masks for Axi Ethernet FCC register */ +#define XAE_FCC_FCRX_MASK	0x20000000 /* Rx flow control enable */ +#define XAE_FCC_FCTX_MASK	0x40000000 /* Tx flow control enable */ + +/* Bit masks for Axi Ethernet EMMC register */ +#define XAE_EMMC_LINKSPEED_MASK	0xC0000000 /* Link speed */ +#define XAE_EMMC_RGMII_MASK	0x20000000 /* RGMII mode enable */ +#define XAE_EMMC_SGMII_MASK	0x10000000 /* SGMII mode enable */ +#define XAE_EMMC_GPCS_MASK	0x08000000 /* 1000BaseX mode enable */ +#define XAE_EMMC_HOST_MASK	0x04000000 /* Host interface enable */ +#define XAE_EMMC_TX16BIT	0x02000000 /* 16 bit Tx client enable */ +#define XAE_EMMC_RX16BIT	0x01000000 /* 16 bit Rx client enable */ +#define XAE_EMMC_LINKSPD_10	0x00000000 /* Link Speed mask for 10 Mbit */ +#define XAE_EMMC_LINKSPD_100	0x40000000 /* Link Speed mask for 100 Mbit */ +#define XAE_EMMC_LINKSPD_1000	0x80000000 /* Link Speed mask for 1000 Mbit */ + +/* Bit masks for Axi Ethernet PHYC register */ +#define XAE_PHYC_SGMIILINKSPEED_MASK	0xC0000000 /* SGMII link speed mask*/ +#define XAE_PHYC_RGMIILINKSPEED_MASK	0x0000000C /* RGMII link speed */ +#define XAE_PHYC_RGMIIHD_MASK		0x00000002 /* RGMII Half-duplex */ +#define XAE_PHYC_RGMIILINK_MASK		0x00000001 /* RGMII link status */ +#define XAE_PHYC_RGLINKSPD_10		0x00000000 /* RGMII link 10 Mbit */ +#define XAE_PHYC_RGLINKSPD_100		0x00000004 /* RGMII link 100 Mbit */ +#define XAE_PHYC_RGLINKSPD_1000		0x00000008 /* RGMII link 1000 Mbit */ +#define XAE_PHYC_SGLINKSPD_10		0x00000000 /* SGMII link 10 Mbit */ +#define XAE_PHYC_SGLINKSPD_100		0x40000000 /* SGMII link 100 Mbit */ +#define XAE_PHYC_SGLINKSPD_1000		0x80000000 /* SGMII link 1000 Mbit */ + +/* Bit masks for Axi Ethernet MDIO interface MC register */ +#define XAE_MDIO_MC_MDIOEN_MASK		0x00000040 /* MII management enable */ +#define XAE_MDIO_MC_CLOCK_DIVIDE_MAX	0x3F	   /* Maximum MDIO divisor */ + +/* Bit masks for Axi Ethernet MDIO interface MCR register */ +#define XAE_MDIO_MCR_PHYAD_MASK		0x1F000000 /* Phy Address Mask */ +#define XAE_MDIO_MCR_PHYAD_SHIFT	24	   /* Phy Address Shift */ +#define XAE_MDIO_MCR_REGAD_MASK		0x001F0000 /* Reg Address Mask */ +#define XAE_MDIO_MCR_REGAD_SHIFT	16	   /* Reg Address Shift */ +#define XAE_MDIO_MCR_OP_MASK		0x0000C000 /* Operation Code Mask */ +#define XAE_MDIO_MCR_OP_SHIFT		13	   /* Operation Code Shift */ +#define XAE_MDIO_MCR_OP_READ_MASK	0x00008000 /* Op Code Read Mask */ +#define XAE_MDIO_MCR_OP_WRITE_MASK	0x00004000 /* Op Code Write Mask */ +#define XAE_MDIO_MCR_INITIATE_MASK	0x00000800 /* Ready Mask */ +#define XAE_MDIO_MCR_READY_MASK		0x00000080 /* Ready Mask */ + +/* Bit masks for Axi Ethernet MDIO interface MIS, MIP, MIE, MIC registers */ +#define XAE_MDIO_INT_MIIM_RDY_MASK	0x00000001 /* MIIM Interrupt */ + +/* Bit masks for Axi Ethernet UAW1 register */ +#define XAE_UAW1_UNICASTADDR_MASK	0x0000FFFF /* Station address bits +						    * [47:32]; Station address +						    * bits [31:0] are stored in +						    * register UAW0 */ + +/* Bit masks for Axi Ethernet FMI register */ +#define XAE_FMI_PM_MASK			0x80000000 /* Promis. mode enable */ +#define XAE_FMI_IND_MASK		0x00000003 /* Index Mask */ + +#define XAE_MDIO_DIV_DFT		29 /* Default MDIO clock divisor */ + +/* Defines for different options for C_PHY_TYPE parameter in Axi Ethernet IP */ +#define XAE_PHY_TYPE_MII		0 +#define XAE_PHY_TYPE_GMII		1 +#define XAE_PHY_TYPE_RGMII_1_3		2 +#define XAE_PHY_TYPE_RGMII_2_0		3 +#define XAE_PHY_TYPE_SGMII		4 +#define XAE_PHY_TYPE_1000BASE_X		5 + +#define XAE_MULTICAST_CAM_TABLE_NUM	4 /* Total number of entries in the +					   * hardware multicast table. */ + +/* Axi Ethernet Synthesis features */ +#define XAE_FEATURE_PARTIAL_RX_CSUM	(1 << 0) +#define XAE_FEATURE_PARTIAL_TX_CSUM	(1 << 1) +#define XAE_FEATURE_FULL_RX_CSUM	(1 << 2) +#define XAE_FEATURE_FULL_TX_CSUM	(1 << 3) + +#define XAE_NO_CSUM_OFFLOAD		0 + +#define XAE_FULL_CSUM_STATUS_MASK	0x00000038 +#define XAE_IP_UDP_CSUM_VALIDATED	0x00000003 +#define XAE_IP_TCP_CSUM_VALIDATED	0x00000002 + +#define DELAY_OF_ONE_MILLISEC		1000 + +/** + * struct axidma_bd - Axi Dma buffer descriptor layout + * @next:         MM2S/S2MM Next Descriptor Pointer + * @reserved1:    Reserved and not used + * @phys:         MM2S/S2MM Buffer Address + * @reserved2:    Reserved and not used + * @reserved3:    Reserved and not used + * @reserved4:    Reserved and not used + * @cntrl:        MM2S/S2MM Control value + * @status:       MM2S/S2MM Status value + * @app0:         MM2S/S2MM User Application Field 0. + * @app1:         MM2S/S2MM User Application Field 1. + * @app2:         MM2S/S2MM User Application Field 2. + * @app3:         MM2S/S2MM User Application Field 3. + * @app4:         MM2S/S2MM User Application Field 4. + * @sw_id_offset: MM2S/S2MM Sw ID + * @reserved5:    Reserved and not used + * @reserved6:    Reserved and not used + */ +struct axidma_bd { +	u32 next;	/* Physical address of next buffer descriptor */ +	u32 reserved1; +	u32 phys; +	u32 reserved2; +	u32 reserved3; +	u32 reserved4; +	u32 cntrl; +	u32 status; +	u32 app0; +	u32 app1;	/* TX start << 16 | insert */ +	u32 app2;	/* TX csum seed */ +	u32 app3; +	u32 app4; +	u32 sw_id_offset; +	u32 reserved5; +	u32 reserved6; +}; + +/** + * struct axienet_local - axienet private per device data + * @ndev:	Pointer for net_device to which it will be attached. + * @dev:	Pointer to device structure + * @phy_dev:	Pointer to PHY device structure attached to the axienet_local + * @phy_node:	Pointer to device node structure + * @mii_bus:	Pointer to MII bus structure + * @mdio_irqs:	IRQs table for MDIO bus required in mii_bus structure + * @regs:	Base address for the axienet_local device address space + * @dma_regs:	Base address for the axidma device address space + * @dma_err_tasklet: Tasklet structure to process Axi DMA errors + * @tx_irq:	Axidma TX IRQ number + * @rx_irq:	Axidma RX IRQ number + * @temac_type:	axienet type to identify between soft and hard temac + * @phy_type:	Phy type to identify between MII/GMII/RGMII/SGMII/1000 Base-X + * @options:	AxiEthernet option word + * @last_link:	Phy link state in which the PHY was negotiated earlier + * @features:	Stores the extended features supported by the axienet hw + * @tx_bd_v:	Virtual address of the TX buffer descriptor ring + * @tx_bd_p:	Physical address(start address) of the TX buffer descr. ring + * @rx_bd_v:	Virtual address of the RX buffer descriptor ring + * @rx_bd_p:	Physical address(start address) of the RX buffer descr. ring + * @tx_bd_ci:	Stores the index of the Tx buffer descriptor in the ring being + *		accessed currently. Used while alloc. BDs before a TX starts + * @tx_bd_tail:	Stores the index of the Tx buffer descriptor in the ring being + *		accessed currently. Used while processing BDs after the TX + *		completed. + * @rx_bd_ci:	Stores the index of the Rx buffer descriptor in the ring being + *		accessed currently. + * @max_frm_size: Stores the maximum size of the frame that can be that + *		  Txed/Rxed in the existing hardware. If jumbo option is + *		  supported, the maximum frame size would be 9k. Else it is + *		  1522 bytes (assuming support for basic VLAN) + * @jumbo_support: Stores hardware configuration for jumbo support. If hardware + *		   can handle jumbo packets, this entry will be 1, else 0. + */ +struct axienet_local { +	struct net_device *ndev; +	struct device *dev; + +	/* Connection to PHY device */ +	struct phy_device *phy_dev;	/* Pointer to PHY device */ +	struct device_node *phy_node; + +	/* MDIO bus data */ +	struct mii_bus *mii_bus;	/* MII bus reference */ +	int mdio_irqs[PHY_MAX_ADDR];	/* IRQs table for MDIO bus */ + +	/* IO registers, dma functions and IRQs */ +	void __iomem *regs; +	void __iomem *dma_regs; + +	struct tasklet_struct dma_err_tasklet; + +	int tx_irq; +	int rx_irq; +	u32 temac_type; +	u32 phy_type; + +	u32 options;			/* Current options word */ +	u32 last_link; +	u32 features; + +	/* Buffer descriptors */ +	struct axidma_bd *tx_bd_v; +	dma_addr_t tx_bd_p; +	struct axidma_bd *rx_bd_v; +	dma_addr_t rx_bd_p; +	u32 tx_bd_ci; +	u32 tx_bd_tail; +	u32 rx_bd_ci; + +	u32 max_frm_size; +	u32 jumbo_support; + +	int csum_offload_on_tx_path; +	int csum_offload_on_rx_path; + +	u32 coalesce_count_rx; +	u32 coalesce_count_tx; +}; + +/** + * struct axiethernet_option - Used to set axi ethernet hardware options + * @opt:	Option to be set. + * @reg:	Register offset to be written for setting the option + * @m_or:	Mask to be ORed for setting the option in the register + */ +struct axienet_option { +	u32 opt; +	u32 reg; +	u32 m_or; +}; + +/** + * axienet_ior - Memory mapped Axi Ethernet register read + * @lp:         Pointer to axienet local structure + * @offset:     Address offset from the base address of Axi Ethernet core + * + * returns: The contents of the Axi Ethernet register + * + * This function returns the contents of the corresponding register. + */ +static inline u32 axienet_ior(struct axienet_local *lp, off_t offset) +{ +	return in_be32(lp->regs + offset); +} + +/** + * axienet_iow - Memory mapped Axi Ethernet register write + * @lp:         Pointer to axienet local structure + * @offset:     Address offset from the base address of Axi Ethernet core + * @value:      Value to be written into the Axi Ethernet register + * + * This function writes the desired value into the corresponding Axi Ethernet + * register. + */ +static inline void axienet_iow(struct axienet_local *lp, off_t offset, +			       u32 value) +{ +	out_be32((lp->regs + offset), value); +} + +/* Function prototypes visible in xilinx_axienet_mdio.c for other files */ +int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np); +int axienet_mdio_wait_until_ready(struct axienet_local *lp); +void axienet_mdio_teardown(struct axienet_local *lp); + +#endif /* XILINX_AXI_ENET_H */ diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c new file mode 100644 index 00000000000..7b0a7355626 --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -0,0 +1,1659 @@ +/* + * Xilinx Axi Ethernet device driver + * + * Copyright (c) 2008 Nissin Systems Co., Ltd.,  Yoshio Kashiwagi + * Copyright (c) 2005-2008 DLA Systems,  David H. Lynch Jr. <dhlii@dlasys.net> + * Copyright (c) 2008-2009 Secret Lab Technologies Ltd. + * Copyright (c) 2010 - 2011 Michal Simek <monstr@monstr.eu> + * Copyright (c) 2010 - 2011 PetaLogix + * Copyright (c) 2010 - 2012 Xilinx, Inc. All rights reserved. + * + * This is a driver for the Xilinx Axi Ethernet which is used in the Virtex6 + * and Spartan6. + * + * TODO: + *  - Add Axi Fifo support. + *  - Factor out Axi DMA code into separate driver. + *  - Test and fix basic multicast filtering. + *  - Add support for extended multicast filtering. + *  - Test basic VLAN support. + *  - Add support for extended VLAN support. + */ + +#include <linux/delay.h> +#include <linux/etherdevice.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/of_mdio.h> +#include <linux/of_platform.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/phy.h> +#include <linux/mii.h> +#include <linux/ethtool.h> + +#include "xilinx_axienet.h" + +/* Descriptors defines for Tx and Rx DMA - 2^n for the best performance */ +#define TX_BD_NUM		64 +#define RX_BD_NUM		128 + +/* Must be shorter than length of ethtool_drvinfo.driver field to fit */ +#define DRIVER_NAME		"xaxienet" +#define DRIVER_DESCRIPTION	"Xilinx Axi Ethernet driver" +#define DRIVER_VERSION		"1.00a" + +#define AXIENET_REGS_N		32 + +/* Match table for of_platform binding */ +static struct of_device_id axienet_of_match[] = { +	{ .compatible = "xlnx,axi-ethernet-1.00.a", }, +	{ .compatible = "xlnx,axi-ethernet-1.01.a", }, +	{ .compatible = "xlnx,axi-ethernet-2.01.a", }, +	{}, +}; + +MODULE_DEVICE_TABLE(of, axienet_of_match); + +/* Option table for setting up Axi Ethernet hardware options */ +static struct axienet_option axienet_options[] = { +	/* Turn on jumbo packet support for both Rx and Tx */ +	{ +		.opt = XAE_OPTION_JUMBO, +		.reg = XAE_TC_OFFSET, +		.m_or = XAE_TC_JUM_MASK, +	}, { +		.opt = XAE_OPTION_JUMBO, +		.reg = XAE_RCW1_OFFSET, +		.m_or = XAE_RCW1_JUM_MASK, +	}, { /* Turn on VLAN packet support for both Rx and Tx */ +		.opt = XAE_OPTION_VLAN, +		.reg = XAE_TC_OFFSET, +		.m_or = XAE_TC_VLAN_MASK, +	}, { +		.opt = XAE_OPTION_VLAN, +		.reg = XAE_RCW1_OFFSET, +		.m_or = XAE_RCW1_VLAN_MASK, +	}, { /* Turn on FCS stripping on receive packets */ +		.opt = XAE_OPTION_FCS_STRIP, +		.reg = XAE_RCW1_OFFSET, +		.m_or = XAE_RCW1_FCS_MASK, +	}, { /* Turn on FCS insertion on transmit packets */ +		.opt = XAE_OPTION_FCS_INSERT, +		.reg = XAE_TC_OFFSET, +		.m_or = XAE_TC_FCS_MASK, +	}, { /* Turn off length/type field checking on receive packets */ +		.opt = XAE_OPTION_LENTYPE_ERR, +		.reg = XAE_RCW1_OFFSET, +		.m_or = XAE_RCW1_LT_DIS_MASK, +	}, { /* Turn on Rx flow control */ +		.opt = XAE_OPTION_FLOW_CONTROL, +		.reg = XAE_FCC_OFFSET, +		.m_or = XAE_FCC_FCRX_MASK, +	}, { /* Turn on Tx flow control */ +		.opt = XAE_OPTION_FLOW_CONTROL, +		.reg = XAE_FCC_OFFSET, +		.m_or = XAE_FCC_FCTX_MASK, +	}, { /* Turn on promiscuous frame filtering */ +		.opt = XAE_OPTION_PROMISC, +		.reg = XAE_FMI_OFFSET, +		.m_or = XAE_FMI_PM_MASK, +	}, { /* Enable transmitter */ +		.opt = XAE_OPTION_TXEN, +		.reg = XAE_TC_OFFSET, +		.m_or = XAE_TC_TX_MASK, +	}, { /* Enable receiver */ +		.opt = XAE_OPTION_RXEN, +		.reg = XAE_RCW1_OFFSET, +		.m_or = XAE_RCW1_RX_MASK, +	}, +	{} +}; + +/** + * axienet_dma_in32 - Memory mapped Axi DMA register read + * @lp:		Pointer to axienet local structure + * @reg:	Address offset from the base address of the Axi DMA core + * + * returns: The contents of the Axi DMA register + * + * This function returns the contents of the corresponding Axi DMA register. + */ +static inline u32 axienet_dma_in32(struct axienet_local *lp, off_t reg) +{ +	return in_be32(lp->dma_regs + reg); +} + +/** + * axienet_dma_out32 - Memory mapped Axi DMA register write. + * @lp:		Pointer to axienet local structure + * @reg:	Address offset from the base address of the Axi DMA core + * @value:	Value to be written into the Axi DMA register + * + * This function writes the desired value into the corresponding Axi DMA + * register. + */ +static inline void axienet_dma_out32(struct axienet_local *lp, +				     off_t reg, u32 value) +{ +	out_be32((lp->dma_regs + reg), value); +} + +/** + * axienet_dma_bd_release - Release buffer descriptor rings + * @ndev:	Pointer to the net_device structure + * + * This function is used to release the descriptors allocated in + * axienet_dma_bd_init. axienet_dma_bd_release is called when Axi Ethernet + * driver stop api is called. + */ +static void axienet_dma_bd_release(struct net_device *ndev) +{ +	int i; +	struct axienet_local *lp = netdev_priv(ndev); + +	for (i = 0; i < RX_BD_NUM; i++) { +		dma_unmap_single(ndev->dev.parent, lp->rx_bd_v[i].phys, +				 lp->max_frm_size, DMA_FROM_DEVICE); +		dev_kfree_skb((struct sk_buff *) +			      (lp->rx_bd_v[i].sw_id_offset)); +	} + +	if (lp->rx_bd_v) { +		dma_free_coherent(ndev->dev.parent, +				  sizeof(*lp->rx_bd_v) * RX_BD_NUM, +				  lp->rx_bd_v, +				  lp->rx_bd_p); +	} +	if (lp->tx_bd_v) { +		dma_free_coherent(ndev->dev.parent, +				  sizeof(*lp->tx_bd_v) * TX_BD_NUM, +				  lp->tx_bd_v, +				  lp->tx_bd_p); +	} +} + +/** + * axienet_dma_bd_init - Setup buffer descriptor rings for Axi DMA + * @ndev:	Pointer to the net_device structure + * + * returns: 0, on success + *	    -ENOMEM, on failure + * + * This function is called to initialize the Rx and Tx DMA descriptor + * rings. This initializes the descriptors with required default values + * and is called when Axi Ethernet driver reset is called. + */ +static int axienet_dma_bd_init(struct net_device *ndev) +{ +	u32 cr; +	int i; +	struct sk_buff *skb; +	struct axienet_local *lp = netdev_priv(ndev); + +	/* Reset the indexes which are used for accessing the BDs */ +	lp->tx_bd_ci = 0; +	lp->tx_bd_tail = 0; +	lp->rx_bd_ci = 0; + +	/* +	 * Allocate the Tx and Rx buffer descriptors. +	 */ +	lp->tx_bd_v = dma_zalloc_coherent(ndev->dev.parent, +					  sizeof(*lp->tx_bd_v) * TX_BD_NUM, +					  &lp->tx_bd_p, GFP_KERNEL); +	if (!lp->tx_bd_v) +		goto out; + +	lp->rx_bd_v = dma_zalloc_coherent(ndev->dev.parent, +					  sizeof(*lp->rx_bd_v) * RX_BD_NUM, +					  &lp->rx_bd_p, GFP_KERNEL); +	if (!lp->rx_bd_v) +		goto out; + +	for (i = 0; i < TX_BD_NUM; i++) { +		lp->tx_bd_v[i].next = lp->tx_bd_p + +				      sizeof(*lp->tx_bd_v) * +				      ((i + 1) % TX_BD_NUM); +	} + +	for (i = 0; i < RX_BD_NUM; i++) { +		lp->rx_bd_v[i].next = lp->rx_bd_p + +				      sizeof(*lp->rx_bd_v) * +				      ((i + 1) % RX_BD_NUM); + +		skb = netdev_alloc_skb_ip_align(ndev, lp->max_frm_size); +		if (!skb) +			goto out; + +		lp->rx_bd_v[i].sw_id_offset = (u32) skb; +		lp->rx_bd_v[i].phys = dma_map_single(ndev->dev.parent, +						     skb->data, +						     lp->max_frm_size, +						     DMA_FROM_DEVICE); +		lp->rx_bd_v[i].cntrl = lp->max_frm_size; +	} + +	/* Start updating the Rx channel control register */ +	cr = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); +	/* Update the interrupt coalesce count */ +	cr = ((cr & ~XAXIDMA_COALESCE_MASK) | +	      ((lp->coalesce_count_rx) << XAXIDMA_COALESCE_SHIFT)); +	/* Update the delay timer count */ +	cr = ((cr & ~XAXIDMA_DELAY_MASK) | +	      (XAXIDMA_DFT_RX_WAITBOUND << XAXIDMA_DELAY_SHIFT)); +	/* Enable coalesce, delay timer and error interrupts */ +	cr |= XAXIDMA_IRQ_ALL_MASK; +	/* Write to the Rx channel control register */ +	axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, cr); + +	/* Start updating the Tx channel control register */ +	cr = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); +	/* Update the interrupt coalesce count */ +	cr = (((cr & ~XAXIDMA_COALESCE_MASK)) | +	      ((lp->coalesce_count_tx) << XAXIDMA_COALESCE_SHIFT)); +	/* Update the delay timer count */ +	cr = (((cr & ~XAXIDMA_DELAY_MASK)) | +	      (XAXIDMA_DFT_TX_WAITBOUND << XAXIDMA_DELAY_SHIFT)); +	/* Enable coalesce, delay timer and error interrupts */ +	cr |= XAXIDMA_IRQ_ALL_MASK; +	/* Write to the Tx channel control register */ +	axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, cr); + +	/* Populate the tail pointer and bring the Rx Axi DMA engine out of +	 * halted state. This will make the Rx side ready for reception.*/ +	axienet_dma_out32(lp, XAXIDMA_RX_CDESC_OFFSET, lp->rx_bd_p); +	cr = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); +	axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, +			  cr | XAXIDMA_CR_RUNSTOP_MASK); +	axienet_dma_out32(lp, XAXIDMA_RX_TDESC_OFFSET, lp->rx_bd_p + +			  (sizeof(*lp->rx_bd_v) * (RX_BD_NUM - 1))); + +	/* Write to the RS (Run-stop) bit in the Tx channel control register. +	 * Tx channel is now ready to run. But only after we write to the +	 * tail pointer register that the Tx channel will start transmitting */ +	axienet_dma_out32(lp, XAXIDMA_TX_CDESC_OFFSET, lp->tx_bd_p); +	cr = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); +	axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, +			  cr | XAXIDMA_CR_RUNSTOP_MASK); + +	return 0; +out: +	axienet_dma_bd_release(ndev); +	return -ENOMEM; +} + +/** + * axienet_set_mac_address - Write the MAC address + * @ndev:	Pointer to the net_device structure + * @address:	6 byte Address to be written as MAC address + * + * This function is called to initialize the MAC address of the Axi Ethernet + * core. It writes to the UAW0 and UAW1 registers of the core. + */ +static void axienet_set_mac_address(struct net_device *ndev, void *address) +{ +	struct axienet_local *lp = netdev_priv(ndev); + +	if (address) +		memcpy(ndev->dev_addr, address, ETH_ALEN); +	if (!is_valid_ether_addr(ndev->dev_addr)) +		eth_random_addr(ndev->dev_addr); + +	/* Set up unicast MAC address filter set its mac address */ +	axienet_iow(lp, XAE_UAW0_OFFSET, +		    (ndev->dev_addr[0]) | +		    (ndev->dev_addr[1] << 8) | +		    (ndev->dev_addr[2] << 16) | +		    (ndev->dev_addr[3] << 24)); +	axienet_iow(lp, XAE_UAW1_OFFSET, +		    (((axienet_ior(lp, XAE_UAW1_OFFSET)) & +		      ~XAE_UAW1_UNICASTADDR_MASK) | +		     (ndev->dev_addr[4] | +		     (ndev->dev_addr[5] << 8)))); +} + +/** + * netdev_set_mac_address - Write the MAC address (from outside the driver) + * @ndev:	Pointer to the net_device structure + * @p:		6 byte Address to be written as MAC address + * + * returns: 0 for all conditions. Presently, there is no failure case. + * + * This function is called to initialize the MAC address of the Axi Ethernet + * core. It calls the core specific axienet_set_mac_address. This is the + * function that goes into net_device_ops structure entry ndo_set_mac_address. + */ +static int netdev_set_mac_address(struct net_device *ndev, void *p) +{ +	struct sockaddr *addr = p; +	axienet_set_mac_address(ndev, addr->sa_data); +	return 0; +} + +/** + * axienet_set_multicast_list - Prepare the multicast table + * @ndev:	Pointer to the net_device structure + * + * This function is called to initialize the multicast table during + * initialization. The Axi Ethernet basic multicast support has a four-entry + * multicast table which is initialized here. Additionally this function + * goes into the net_device_ops structure entry ndo_set_multicast_list. This + * means whenever the multicast table entries need to be updated this + * function gets called. + */ +static void axienet_set_multicast_list(struct net_device *ndev) +{ +	int i; +	u32 reg, af0reg, af1reg; +	struct axienet_local *lp = netdev_priv(ndev); + +	if (ndev->flags & (IFF_ALLMULTI | IFF_PROMISC) || +	    netdev_mc_count(ndev) > XAE_MULTICAST_CAM_TABLE_NUM) { +		/* We must make the kernel realize we had to move into +		 * promiscuous mode. If it was a promiscuous mode request +		 * the flag is already set. If not we set it. */ +		ndev->flags |= IFF_PROMISC; +		reg = axienet_ior(lp, XAE_FMI_OFFSET); +		reg |= XAE_FMI_PM_MASK; +		axienet_iow(lp, XAE_FMI_OFFSET, reg); +		dev_info(&ndev->dev, "Promiscuous mode enabled.\n"); +	} else if (!netdev_mc_empty(ndev)) { +		struct netdev_hw_addr *ha; + +		i = 0; +		netdev_for_each_mc_addr(ha, ndev) { +			if (i >= XAE_MULTICAST_CAM_TABLE_NUM) +				break; + +			af0reg = (ha->addr[0]); +			af0reg |= (ha->addr[1] << 8); +			af0reg |= (ha->addr[2] << 16); +			af0reg |= (ha->addr[3] << 24); + +			af1reg = (ha->addr[4]); +			af1reg |= (ha->addr[5] << 8); + +			reg = axienet_ior(lp, XAE_FMI_OFFSET) & 0xFFFFFF00; +			reg |= i; + +			axienet_iow(lp, XAE_FMI_OFFSET, reg); +			axienet_iow(lp, XAE_AF0_OFFSET, af0reg); +			axienet_iow(lp, XAE_AF1_OFFSET, af1reg); +			i++; +		} +	} else { +		reg = axienet_ior(lp, XAE_FMI_OFFSET); +		reg &= ~XAE_FMI_PM_MASK; + +		axienet_iow(lp, XAE_FMI_OFFSET, reg); + +		for (i = 0; i < XAE_MULTICAST_CAM_TABLE_NUM; i++) { +			reg = axienet_ior(lp, XAE_FMI_OFFSET) & 0xFFFFFF00; +			reg |= i; + +			axienet_iow(lp, XAE_FMI_OFFSET, reg); +			axienet_iow(lp, XAE_AF0_OFFSET, 0); +			axienet_iow(lp, XAE_AF1_OFFSET, 0); +		} + +		dev_info(&ndev->dev, "Promiscuous mode disabled.\n"); +	} +} + +/** + * axienet_setoptions - Set an Axi Ethernet option + * @ndev:	Pointer to the net_device structure + * @options:	Option to be enabled/disabled + * + * The Axi Ethernet core has multiple features which can be selectively turned + * on or off. The typical options could be jumbo frame option, basic VLAN + * option, promiscuous mode option etc. This function is used to set or clear + * these options in the Axi Ethernet hardware. This is done through + * axienet_option structure . + */ +static void axienet_setoptions(struct net_device *ndev, u32 options) +{ +	int reg; +	struct axienet_local *lp = netdev_priv(ndev); +	struct axienet_option *tp = &axienet_options[0]; + +	while (tp->opt) { +		reg = ((axienet_ior(lp, tp->reg)) & ~(tp->m_or)); +		if (options & tp->opt) +			reg |= tp->m_or; +		axienet_iow(lp, tp->reg, reg); +		tp++; +	} + +	lp->options |= options; +} + +static void __axienet_device_reset(struct axienet_local *lp, +				   struct device *dev, off_t offset) +{ +	u32 timeout; +	/* Reset Axi DMA. This would reset Axi Ethernet core as well. The reset +	 * process of Axi DMA takes a while to complete as all pending +	 * commands/transfers will be flushed or completed during this +	 * reset process. */ +	axienet_dma_out32(lp, offset, XAXIDMA_CR_RESET_MASK); +	timeout = DELAY_OF_ONE_MILLISEC; +	while (axienet_dma_in32(lp, offset) & XAXIDMA_CR_RESET_MASK) { +		udelay(1); +		if (--timeout == 0) { +			dev_err(dev, "axienet_device_reset DMA " +				"reset timeout!\n"); +			break; +		} +	} +} + +/** + * axienet_device_reset - Reset and initialize the Axi Ethernet hardware. + * @ndev:	Pointer to the net_device structure + * + * This function is called to reset and initialize the Axi Ethernet core. This + * is typically called during initialization. It does a reset of the Axi DMA + * Rx/Tx channels and initializes the Axi DMA BDs. Since Axi DMA reset lines + * areconnected to Axi Ethernet reset lines, this in turn resets the Axi + * Ethernet core. No separate hardware reset is done for the Axi Ethernet + * core. + */ +static void axienet_device_reset(struct net_device *ndev) +{ +	u32 axienet_status; +	struct axienet_local *lp = netdev_priv(ndev); + +	__axienet_device_reset(lp, &ndev->dev, XAXIDMA_TX_CR_OFFSET); +	__axienet_device_reset(lp, &ndev->dev, XAXIDMA_RX_CR_OFFSET); + +	lp->max_frm_size = XAE_MAX_VLAN_FRAME_SIZE; +	lp->options &= (~XAE_OPTION_JUMBO); + +	if ((ndev->mtu > XAE_MTU) && +	    (ndev->mtu <= XAE_JUMBO_MTU) && +	    (lp->jumbo_support)) { +		lp->max_frm_size = ndev->mtu + XAE_HDR_VLAN_SIZE + +				   XAE_TRL_SIZE; +		lp->options |= XAE_OPTION_JUMBO; +	} + +	if (axienet_dma_bd_init(ndev)) { +		dev_err(&ndev->dev, "axienet_device_reset descriptor " +			"allocation failed\n"); +	} + +	axienet_status = axienet_ior(lp, XAE_RCW1_OFFSET); +	axienet_status &= ~XAE_RCW1_RX_MASK; +	axienet_iow(lp, XAE_RCW1_OFFSET, axienet_status); + +	axienet_status = axienet_ior(lp, XAE_IP_OFFSET); +	if (axienet_status & XAE_INT_RXRJECT_MASK) +		axienet_iow(lp, XAE_IS_OFFSET, XAE_INT_RXRJECT_MASK); + +	axienet_iow(lp, XAE_FCC_OFFSET, XAE_FCC_FCRX_MASK); + +	/* Sync default options with HW but leave receiver and +	 * transmitter disabled.*/ +	axienet_setoptions(ndev, lp->options & +			   ~(XAE_OPTION_TXEN | XAE_OPTION_RXEN)); +	axienet_set_mac_address(ndev, NULL); +	axienet_set_multicast_list(ndev); +	axienet_setoptions(ndev, lp->options); + +	ndev->trans_start = jiffies; +} + +/** + * axienet_adjust_link - Adjust the PHY link speed/duplex. + * @ndev:	Pointer to the net_device structure + * + * This function is called to change the speed and duplex setting after + * auto negotiation is done by the PHY. This is the function that gets + * registered with the PHY interface through the "of_phy_connect" call. + */ +static void axienet_adjust_link(struct net_device *ndev) +{ +	u32 emmc_reg; +	u32 link_state; +	u32 setspeed = 1; +	struct axienet_local *lp = netdev_priv(ndev); +	struct phy_device *phy = lp->phy_dev; + +	link_state = phy->speed | (phy->duplex << 1) | phy->link; +	if (lp->last_link != link_state) { +		if ((phy->speed == SPEED_10) || (phy->speed == SPEED_100)) { +			if (lp->phy_type == XAE_PHY_TYPE_1000BASE_X) +				setspeed = 0; +		} else { +			if ((phy->speed == SPEED_1000) && +			    (lp->phy_type == XAE_PHY_TYPE_MII)) +				setspeed = 0; +		} + +		if (setspeed == 1) { +			emmc_reg = axienet_ior(lp, XAE_EMMC_OFFSET); +			emmc_reg &= ~XAE_EMMC_LINKSPEED_MASK; + +			switch (phy->speed) { +			case SPEED_1000: +				emmc_reg |= XAE_EMMC_LINKSPD_1000; +				break; +			case SPEED_100: +				emmc_reg |= XAE_EMMC_LINKSPD_100; +				break; +			case SPEED_10: +				emmc_reg |= XAE_EMMC_LINKSPD_10; +				break; +			default: +				dev_err(&ndev->dev, "Speed other than 10, 100 " +					"or 1Gbps is not supported\n"); +				break; +			} + +			axienet_iow(lp, XAE_EMMC_OFFSET, emmc_reg); +			lp->last_link = link_state; +			phy_print_status(phy); +		} else { +			dev_err(&ndev->dev, "Error setting Axi Ethernet " +				"mac speed\n"); +		} +	} +} + +/** + * axienet_start_xmit_done - Invoked once a transmit is completed by the + * Axi DMA Tx channel. + * @ndev:	Pointer to the net_device structure + * + * This function is invoked from the Axi DMA Tx isr to notify the completion + * of transmit operation. It clears fields in the corresponding Tx BDs and + * unmaps the corresponding buffer so that CPU can regain ownership of the + * buffer. It finally invokes "netif_wake_queue" to restart transmission if + * required. + */ +static void axienet_start_xmit_done(struct net_device *ndev) +{ +	u32 size = 0; +	u32 packets = 0; +	struct axienet_local *lp = netdev_priv(ndev); +	struct axidma_bd *cur_p; +	unsigned int status = 0; + +	cur_p = &lp->tx_bd_v[lp->tx_bd_ci]; +	status = cur_p->status; +	while (status & XAXIDMA_BD_STS_COMPLETE_MASK) { +		dma_unmap_single(ndev->dev.parent, cur_p->phys, +				(cur_p->cntrl & XAXIDMA_BD_CTRL_LENGTH_MASK), +				DMA_TO_DEVICE); +		if (cur_p->app4) +			dev_kfree_skb_irq((struct sk_buff *)cur_p->app4); +		/*cur_p->phys = 0;*/ +		cur_p->app0 = 0; +		cur_p->app1 = 0; +		cur_p->app2 = 0; +		cur_p->app4 = 0; +		cur_p->status = 0; + +		size += status & XAXIDMA_BD_STS_ACTUAL_LEN_MASK; +		packets++; + +		++lp->tx_bd_ci; +		lp->tx_bd_ci %= TX_BD_NUM; +		cur_p = &lp->tx_bd_v[lp->tx_bd_ci]; +		status = cur_p->status; +	} + +	ndev->stats.tx_packets += packets; +	ndev->stats.tx_bytes += size; +	netif_wake_queue(ndev); +} + +/** + * axienet_check_tx_bd_space - Checks if a BD/group of BDs are currently busy + * @lp:		Pointer to the axienet_local structure + * @num_frag:	The number of BDs to check for + * + * returns: 0, on success + *	    NETDEV_TX_BUSY, if any of the descriptors are not free + * + * This function is invoked before BDs are allocated and transmission starts. + * This function returns 0 if a BD or group of BDs can be allocated for + * transmission. If the BD or any of the BDs are not free the function + * returns a busy status. This is invoked from axienet_start_xmit. + */ +static inline int axienet_check_tx_bd_space(struct axienet_local *lp, +					    int num_frag) +{ +	struct axidma_bd *cur_p; +	cur_p = &lp->tx_bd_v[(lp->tx_bd_tail + num_frag) % TX_BD_NUM]; +	if (cur_p->status & XAXIDMA_BD_STS_ALL_MASK) +		return NETDEV_TX_BUSY; +	return 0; +} + +/** + * axienet_start_xmit - Starts the transmission. + * @skb:	sk_buff pointer that contains data to be Txed. + * @ndev:	Pointer to net_device structure. + * + * returns: NETDEV_TX_OK, on success + *	    NETDEV_TX_BUSY, if any of the descriptors are not free + * + * This function is invoked from upper layers to initiate transmission. The + * function uses the next available free BDs and populates their fields to + * start the transmission. Additionally if checksum offloading is supported, + * it populates AXI Stream Control fields with appropriate values. + */ +static int axienet_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ +	u32 ii; +	u32 num_frag; +	u32 csum_start_off; +	u32 csum_index_off; +	skb_frag_t *frag; +	dma_addr_t tail_p; +	struct axienet_local *lp = netdev_priv(ndev); +	struct axidma_bd *cur_p; + +	num_frag = skb_shinfo(skb)->nr_frags; +	cur_p = &lp->tx_bd_v[lp->tx_bd_tail]; + +	if (axienet_check_tx_bd_space(lp, num_frag)) { +		if (!netif_queue_stopped(ndev)) +			netif_stop_queue(ndev); +		return NETDEV_TX_BUSY; +	} + +	if (skb->ip_summed == CHECKSUM_PARTIAL) { +		if (lp->features & XAE_FEATURE_FULL_TX_CSUM) { +			/* Tx Full Checksum Offload Enabled */ +			cur_p->app0 |= 2; +		} else if (lp->features & XAE_FEATURE_PARTIAL_RX_CSUM) { +			csum_start_off = skb_transport_offset(skb); +			csum_index_off = csum_start_off + skb->csum_offset; +			/* Tx Partial Checksum Offload Enabled */ +			cur_p->app0 |= 1; +			cur_p->app1 = (csum_start_off << 16) | csum_index_off; +		} +	} else if (skb->ip_summed == CHECKSUM_UNNECESSARY) { +		cur_p->app0 |= 2; /* Tx Full Checksum Offload Enabled */ +	} + +	cur_p->cntrl = skb_headlen(skb) | XAXIDMA_BD_CTRL_TXSOF_MASK; +	cur_p->phys = dma_map_single(ndev->dev.parent, skb->data, +				     skb_headlen(skb), DMA_TO_DEVICE); + +	for (ii = 0; ii < num_frag; ii++) { +		++lp->tx_bd_tail; +		lp->tx_bd_tail %= TX_BD_NUM; +		cur_p = &lp->tx_bd_v[lp->tx_bd_tail]; +		frag = &skb_shinfo(skb)->frags[ii]; +		cur_p->phys = dma_map_single(ndev->dev.parent, +					     skb_frag_address(frag), +					     skb_frag_size(frag), +					     DMA_TO_DEVICE); +		cur_p->cntrl = skb_frag_size(frag); +	} + +	cur_p->cntrl |= XAXIDMA_BD_CTRL_TXEOF_MASK; +	cur_p->app4 = (unsigned long)skb; + +	tail_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail; +	/* Start the transfer */ +	axienet_dma_out32(lp, XAXIDMA_TX_TDESC_OFFSET, tail_p); +	++lp->tx_bd_tail; +	lp->tx_bd_tail %= TX_BD_NUM; + +	return NETDEV_TX_OK; +} + +/** + * axienet_recv - Is called from Axi DMA Rx Isr to complete the received + *		  BD processing. + * @ndev:	Pointer to net_device structure. + * + * This function is invoked from the Axi DMA Rx isr to process the Rx BDs. It + * does minimal processing and invokes "netif_rx" to complete further + * processing. + */ +static void axienet_recv(struct net_device *ndev) +{ +	u32 length; +	u32 csumstatus; +	u32 size = 0; +	u32 packets = 0; +	dma_addr_t tail_p; +	struct axienet_local *lp = netdev_priv(ndev); +	struct sk_buff *skb, *new_skb; +	struct axidma_bd *cur_p; + +	tail_p = lp->rx_bd_p + sizeof(*lp->rx_bd_v) * lp->rx_bd_ci; +	cur_p = &lp->rx_bd_v[lp->rx_bd_ci]; + +	while ((cur_p->status & XAXIDMA_BD_STS_COMPLETE_MASK)) { +		skb = (struct sk_buff *) (cur_p->sw_id_offset); +		length = cur_p->app4 & 0x0000FFFF; + +		dma_unmap_single(ndev->dev.parent, cur_p->phys, +				 lp->max_frm_size, +				 DMA_FROM_DEVICE); + +		skb_put(skb, length); +		skb->protocol = eth_type_trans(skb, ndev); +		/*skb_checksum_none_assert(skb);*/ +		skb->ip_summed = CHECKSUM_NONE; + +		/* if we're doing Rx csum offload, set it up */ +		if (lp->features & XAE_FEATURE_FULL_RX_CSUM) { +			csumstatus = (cur_p->app2 & +				      XAE_FULL_CSUM_STATUS_MASK) >> 3; +			if ((csumstatus == XAE_IP_TCP_CSUM_VALIDATED) || +			    (csumstatus == XAE_IP_UDP_CSUM_VALIDATED)) { +				skb->ip_summed = CHECKSUM_UNNECESSARY; +			} +		} else if ((lp->features & XAE_FEATURE_PARTIAL_RX_CSUM) != 0 && +			   skb->protocol == htons(ETH_P_IP) && +			   skb->len > 64) { +			skb->csum = be32_to_cpu(cur_p->app3 & 0xFFFF); +			skb->ip_summed = CHECKSUM_COMPLETE; +		} + +		netif_rx(skb); + +		size += length; +		packets++; + +		new_skb = netdev_alloc_skb_ip_align(ndev, lp->max_frm_size); +		if (!new_skb) +			return; + +		cur_p->phys = dma_map_single(ndev->dev.parent, new_skb->data, +					     lp->max_frm_size, +					     DMA_FROM_DEVICE); +		cur_p->cntrl = lp->max_frm_size; +		cur_p->status = 0; +		cur_p->sw_id_offset = (u32) new_skb; + +		++lp->rx_bd_ci; +		lp->rx_bd_ci %= RX_BD_NUM; +		cur_p = &lp->rx_bd_v[lp->rx_bd_ci]; +	} + +	ndev->stats.rx_packets += packets; +	ndev->stats.rx_bytes += size; + +	axienet_dma_out32(lp, XAXIDMA_RX_TDESC_OFFSET, tail_p); +} + +/** + * axienet_tx_irq - Tx Done Isr. + * @irq:	irq number + * @_ndev:	net_device pointer + * + * returns: IRQ_HANDLED for all cases. + * + * This is the Axi DMA Tx done Isr. It invokes "axienet_start_xmit_done" + * to complete the BD processing. + */ +static irqreturn_t axienet_tx_irq(int irq, void *_ndev) +{ +	u32 cr; +	unsigned int status; +	struct net_device *ndev = _ndev; +	struct axienet_local *lp = netdev_priv(ndev); + +	status = axienet_dma_in32(lp, XAXIDMA_TX_SR_OFFSET); +	if (status & (XAXIDMA_IRQ_IOC_MASK | XAXIDMA_IRQ_DELAY_MASK)) { +		axienet_start_xmit_done(lp->ndev); +		goto out; +	} +	if (!(status & XAXIDMA_IRQ_ALL_MASK)) +		dev_err(&ndev->dev, "No interrupts asserted in Tx path"); +	if (status & XAXIDMA_IRQ_ERROR_MASK) { +		dev_err(&ndev->dev, "DMA Tx error 0x%x\n", status); +		dev_err(&ndev->dev, "Current BD is at: 0x%x\n", +			(lp->tx_bd_v[lp->tx_bd_ci]).phys); + +		cr = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); +		/* Disable coalesce, delay timer and error interrupts */ +		cr &= (~XAXIDMA_IRQ_ALL_MASK); +		/* Write to the Tx channel control register */ +		axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, cr); + +		cr = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); +		/* Disable coalesce, delay timer and error interrupts */ +		cr &= (~XAXIDMA_IRQ_ALL_MASK); +		/* Write to the Rx channel control register */ +		axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, cr); + +		tasklet_schedule(&lp->dma_err_tasklet); +	} +out: +	axienet_dma_out32(lp, XAXIDMA_TX_SR_OFFSET, status); +	return IRQ_HANDLED; +} + +/** + * axienet_rx_irq - Rx Isr. + * @irq:	irq number + * @_ndev:	net_device pointer + * + * returns: IRQ_HANDLED for all cases. + * + * This is the Axi DMA Rx Isr. It invokes "axienet_recv" to complete the BD + * processing. + */ +static irqreturn_t axienet_rx_irq(int irq, void *_ndev) +{ +	u32 cr; +	unsigned int status; +	struct net_device *ndev = _ndev; +	struct axienet_local *lp = netdev_priv(ndev); + +	status = axienet_dma_in32(lp, XAXIDMA_RX_SR_OFFSET); +	if (status & (XAXIDMA_IRQ_IOC_MASK | XAXIDMA_IRQ_DELAY_MASK)) { +		axienet_recv(lp->ndev); +		goto out; +	} +	if (!(status & XAXIDMA_IRQ_ALL_MASK)) +		dev_err(&ndev->dev, "No interrupts asserted in Rx path"); +	if (status & XAXIDMA_IRQ_ERROR_MASK) { +		dev_err(&ndev->dev, "DMA Rx error 0x%x\n", status); +		dev_err(&ndev->dev, "Current BD is at: 0x%x\n", +			(lp->rx_bd_v[lp->rx_bd_ci]).phys); + +		cr = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); +		/* Disable coalesce, delay timer and error interrupts */ +		cr &= (~XAXIDMA_IRQ_ALL_MASK); +		/* Finally write to the Tx channel control register */ +		axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, cr); + +		cr = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); +		/* Disable coalesce, delay timer and error interrupts */ +		cr &= (~XAXIDMA_IRQ_ALL_MASK); +		/* write to the Rx channel control register */ +		axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, cr); + +		tasklet_schedule(&lp->dma_err_tasklet); +	} +out: +	axienet_dma_out32(lp, XAXIDMA_RX_SR_OFFSET, status); +	return IRQ_HANDLED; +} + +static void axienet_dma_err_handler(unsigned long data); + +/** + * axienet_open - Driver open routine. + * @ndev:	Pointer to net_device structure + * + * returns: 0, on success. + *	    -ENODEV, if PHY cannot be connected to + *	    non-zero error value on failure + * + * This is the driver open routine. It calls phy_start to start the PHY device. + * It also allocates interrupt service routines, enables the interrupt lines + * and ISR handling. Axi Ethernet core is reset through Axi DMA core. Buffer + * descriptors are initialized. + */ +static int axienet_open(struct net_device *ndev) +{ +	int ret, mdio_mcreg; +	struct axienet_local *lp = netdev_priv(ndev); + +	dev_dbg(&ndev->dev, "axienet_open()\n"); + +	mdio_mcreg = axienet_ior(lp, XAE_MDIO_MC_OFFSET); +	ret = axienet_mdio_wait_until_ready(lp); +	if (ret < 0) +		return ret; +	/* Disable the MDIO interface till Axi Ethernet Reset is completed. +	 * When we do an Axi Ethernet reset, it resets the complete core +	 * including the MDIO. If MDIO is not disabled when the reset +	 * process is started, MDIO will be broken afterwards. */ +	axienet_iow(lp, XAE_MDIO_MC_OFFSET, +		    (mdio_mcreg & (~XAE_MDIO_MC_MDIOEN_MASK))); +	axienet_device_reset(ndev); +	/* Enable the MDIO */ +	axienet_iow(lp, XAE_MDIO_MC_OFFSET, mdio_mcreg); +	ret = axienet_mdio_wait_until_ready(lp); +	if (ret < 0) +		return ret; + +	if (lp->phy_node) { +		lp->phy_dev = of_phy_connect(lp->ndev, lp->phy_node, +					     axienet_adjust_link, 0, +					     PHY_INTERFACE_MODE_GMII); +		if (!lp->phy_dev) { +			dev_err(lp->dev, "of_phy_connect() failed\n"); +			return -ENODEV; +		} +		phy_start(lp->phy_dev); +	} + +	/* Enable tasklets for Axi DMA error handling */ +	tasklet_init(&lp->dma_err_tasklet, axienet_dma_err_handler, +		     (unsigned long) lp); + +	/* Enable interrupts for Axi DMA Tx */ +	ret = request_irq(lp->tx_irq, axienet_tx_irq, 0, ndev->name, ndev); +	if (ret) +		goto err_tx_irq; +	/* Enable interrupts for Axi DMA Rx */ +	ret = request_irq(lp->rx_irq, axienet_rx_irq, 0, ndev->name, ndev); +	if (ret) +		goto err_rx_irq; + +	return 0; + +err_rx_irq: +	free_irq(lp->tx_irq, ndev); +err_tx_irq: +	if (lp->phy_dev) +		phy_disconnect(lp->phy_dev); +	lp->phy_dev = NULL; +	tasklet_kill(&lp->dma_err_tasklet); +	dev_err(lp->dev, "request_irq() failed\n"); +	return ret; +} + +/** + * axienet_stop - Driver stop routine. + * @ndev:	Pointer to net_device structure + * + * returns: 0, on success. + * + * This is the driver stop routine. It calls phy_disconnect to stop the PHY + * device. It also removes the interrupt handlers and disables the interrupts. + * The Axi DMA Tx/Rx BDs are released. + */ +static int axienet_stop(struct net_device *ndev) +{ +	u32 cr; +	struct axienet_local *lp = netdev_priv(ndev); + +	dev_dbg(&ndev->dev, "axienet_close()\n"); + +	cr = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); +	axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, +			  cr & (~XAXIDMA_CR_RUNSTOP_MASK)); +	cr = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); +	axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, +			  cr & (~XAXIDMA_CR_RUNSTOP_MASK)); +	axienet_setoptions(ndev, lp->options & +			   ~(XAE_OPTION_TXEN | XAE_OPTION_RXEN)); + +	tasklet_kill(&lp->dma_err_tasklet); + +	free_irq(lp->tx_irq, ndev); +	free_irq(lp->rx_irq, ndev); + +	if (lp->phy_dev) +		phy_disconnect(lp->phy_dev); +	lp->phy_dev = NULL; + +	axienet_dma_bd_release(ndev); +	return 0; +} + +/** + * axienet_change_mtu - Driver change mtu routine. + * @ndev:	Pointer to net_device structure + * @new_mtu:	New mtu value to be applied + * + * returns: Always returns 0 (success). + * + * This is the change mtu driver routine. It checks if the Axi Ethernet + * hardware supports jumbo frames before changing the mtu. This can be + * called only when the device is not up. + */ +static int axienet_change_mtu(struct net_device *ndev, int new_mtu) +{ +	struct axienet_local *lp = netdev_priv(ndev); + +	if (netif_running(ndev)) +		return -EBUSY; +	if (lp->jumbo_support) { +		if ((new_mtu > XAE_JUMBO_MTU) || (new_mtu < 64)) +			return -EINVAL; +		ndev->mtu = new_mtu; +	} else { +		if ((new_mtu > XAE_MTU) || (new_mtu < 64)) +			return -EINVAL; +		ndev->mtu = new_mtu; +	} + +	return 0; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +/** + * axienet_poll_controller - Axi Ethernet poll mechanism. + * @ndev:	Pointer to net_device structure + * + * This implements Rx/Tx ISR poll mechanisms. The interrupts are disabled prior + * to polling the ISRs and are enabled back after the polling is done. + */ +static void axienet_poll_controller(struct net_device *ndev) +{ +	struct axienet_local *lp = netdev_priv(ndev); +	disable_irq(lp->tx_irq); +	disable_irq(lp->rx_irq); +	axienet_rx_irq(lp->tx_irq, ndev); +	axienet_tx_irq(lp->rx_irq, ndev); +	enable_irq(lp->tx_irq); +	enable_irq(lp->rx_irq); +} +#endif + +static const struct net_device_ops axienet_netdev_ops = { +	.ndo_open = axienet_open, +	.ndo_stop = axienet_stop, +	.ndo_start_xmit = axienet_start_xmit, +	.ndo_change_mtu	= axienet_change_mtu, +	.ndo_set_mac_address = netdev_set_mac_address, +	.ndo_validate_addr = eth_validate_addr, +	.ndo_set_rx_mode = axienet_set_multicast_list, +#ifdef CONFIG_NET_POLL_CONTROLLER +	.ndo_poll_controller = axienet_poll_controller, +#endif +}; + +/** + * axienet_ethtools_get_settings - Get Axi Ethernet settings related to PHY. + * @ndev:	Pointer to net_device structure + * @ecmd:	Pointer to ethtool_cmd structure + * + * This implements ethtool command for getting PHY settings. If PHY could + * not be found, the function returns -ENODEV. This function calls the + * relevant PHY ethtool API to get the PHY settings. + * Issue "ethtool ethX" under linux prompt to execute this function. + */ +static int axienet_ethtools_get_settings(struct net_device *ndev, +					 struct ethtool_cmd *ecmd) +{ +	struct axienet_local *lp = netdev_priv(ndev); +	struct phy_device *phydev = lp->phy_dev; +	if (!phydev) +		return -ENODEV; +	return phy_ethtool_gset(phydev, ecmd); +} + +/** + * axienet_ethtools_set_settings - Set PHY settings as passed in the argument. + * @ndev:	Pointer to net_device structure + * @ecmd:	Pointer to ethtool_cmd structure + * + * This implements ethtool command for setting various PHY settings. If PHY + * could not be found, the function returns -ENODEV. This function calls the + * relevant PHY ethtool API to set the PHY. + * Issue e.g. "ethtool -s ethX speed 1000" under linux prompt to execute this + * function. + */ +static int axienet_ethtools_set_settings(struct net_device *ndev, +					 struct ethtool_cmd *ecmd) +{ +	struct axienet_local *lp = netdev_priv(ndev); +	struct phy_device *phydev = lp->phy_dev; +	if (!phydev) +		return -ENODEV; +	return phy_ethtool_sset(phydev, ecmd); +} + +/** + * axienet_ethtools_get_drvinfo - Get various Axi Ethernet driver information. + * @ndev:	Pointer to net_device structure + * @ed:		Pointer to ethtool_drvinfo structure + * + * This implements ethtool command for getting the driver information. + * Issue "ethtool -i ethX" under linux prompt to execute this function. + */ +static void axienet_ethtools_get_drvinfo(struct net_device *ndev, +					 struct ethtool_drvinfo *ed) +{ +	strlcpy(ed->driver, DRIVER_NAME, sizeof(ed->driver)); +	strlcpy(ed->version, DRIVER_VERSION, sizeof(ed->version)); +	ed->regdump_len = sizeof(u32) * AXIENET_REGS_N; +} + +/** + * axienet_ethtools_get_regs_len - Get the total regs length present in the + *				   AxiEthernet core. + * @ndev:	Pointer to net_device structure + * + * This implements ethtool command for getting the total register length + * information. + */ +static int axienet_ethtools_get_regs_len(struct net_device *ndev) +{ +	return sizeof(u32) * AXIENET_REGS_N; +} + +/** + * axienet_ethtools_get_regs - Dump the contents of all registers present + *			       in AxiEthernet core. + * @ndev:	Pointer to net_device structure + * @regs:	Pointer to ethtool_regs structure + * @ret:	Void pointer used to return the contents of the registers. + * + * This implements ethtool command for getting the Axi Ethernet register dump. + * Issue "ethtool -d ethX" to execute this function. + */ +static void axienet_ethtools_get_regs(struct net_device *ndev, +				      struct ethtool_regs *regs, void *ret) +{ +	u32 *data = (u32 *) ret; +	size_t len = sizeof(u32) * AXIENET_REGS_N; +	struct axienet_local *lp = netdev_priv(ndev); + +	regs->version = 0; +	regs->len = len; + +	memset(data, 0, len); +	data[0] = axienet_ior(lp, XAE_RAF_OFFSET); +	data[1] = axienet_ior(lp, XAE_TPF_OFFSET); +	data[2] = axienet_ior(lp, XAE_IFGP_OFFSET); +	data[3] = axienet_ior(lp, XAE_IS_OFFSET); +	data[4] = axienet_ior(lp, XAE_IP_OFFSET); +	data[5] = axienet_ior(lp, XAE_IE_OFFSET); +	data[6] = axienet_ior(lp, XAE_TTAG_OFFSET); +	data[7] = axienet_ior(lp, XAE_RTAG_OFFSET); +	data[8] = axienet_ior(lp, XAE_UAWL_OFFSET); +	data[9] = axienet_ior(lp, XAE_UAWU_OFFSET); +	data[10] = axienet_ior(lp, XAE_TPID0_OFFSET); +	data[11] = axienet_ior(lp, XAE_TPID1_OFFSET); +	data[12] = axienet_ior(lp, XAE_PPST_OFFSET); +	data[13] = axienet_ior(lp, XAE_RCW0_OFFSET); +	data[14] = axienet_ior(lp, XAE_RCW1_OFFSET); +	data[15] = axienet_ior(lp, XAE_TC_OFFSET); +	data[16] = axienet_ior(lp, XAE_FCC_OFFSET); +	data[17] = axienet_ior(lp, XAE_EMMC_OFFSET); +	data[18] = axienet_ior(lp, XAE_PHYC_OFFSET); +	data[19] = axienet_ior(lp, XAE_MDIO_MC_OFFSET); +	data[20] = axienet_ior(lp, XAE_MDIO_MCR_OFFSET); +	data[21] = axienet_ior(lp, XAE_MDIO_MWD_OFFSET); +	data[22] = axienet_ior(lp, XAE_MDIO_MRD_OFFSET); +	data[23] = axienet_ior(lp, XAE_MDIO_MIS_OFFSET); +	data[24] = axienet_ior(lp, XAE_MDIO_MIP_OFFSET); +	data[25] = axienet_ior(lp, XAE_MDIO_MIE_OFFSET); +	data[26] = axienet_ior(lp, XAE_MDIO_MIC_OFFSET); +	data[27] = axienet_ior(lp, XAE_UAW0_OFFSET); +	data[28] = axienet_ior(lp, XAE_UAW1_OFFSET); +	data[29] = axienet_ior(lp, XAE_FMI_OFFSET); +	data[30] = axienet_ior(lp, XAE_AF0_OFFSET); +	data[31] = axienet_ior(lp, XAE_AF1_OFFSET); +} + +/** + * axienet_ethtools_get_pauseparam - Get the pause parameter setting for + *				     Tx and Rx paths. + * @ndev:	Pointer to net_device structure + * @epauseparm:	Pointer to ethtool_pauseparam structure. + * + * This implements ethtool command for getting axi ethernet pause frame + * setting. Issue "ethtool -a ethX" to execute this function. + */ +static void +axienet_ethtools_get_pauseparam(struct net_device *ndev, +				struct ethtool_pauseparam *epauseparm) +{ +	u32 regval; +	struct axienet_local *lp = netdev_priv(ndev); +	epauseparm->autoneg  = 0; +	regval = axienet_ior(lp, XAE_FCC_OFFSET); +	epauseparm->tx_pause = regval & XAE_FCC_FCTX_MASK; +	epauseparm->rx_pause = regval & XAE_FCC_FCRX_MASK; +} + +/** + * axienet_ethtools_set_pauseparam - Set device pause parameter(flow control) + *				     settings. + * @ndev:	Pointer to net_device structure + * @epauseparam:Pointer to ethtool_pauseparam structure + * + * This implements ethtool command for enabling flow control on Rx and Tx + * paths. Issue "ethtool -A ethX tx on|off" under linux prompt to execute this + * function. + */ +static int +axienet_ethtools_set_pauseparam(struct net_device *ndev, +				struct ethtool_pauseparam *epauseparm) +{ +	u32 regval = 0; +	struct axienet_local *lp = netdev_priv(ndev); + +	if (netif_running(ndev)) { +		printk(KERN_ERR	"%s: Please stop netif before applying " +		       "configruation\n", ndev->name); +		return -EFAULT; +	} + +	regval = axienet_ior(lp, XAE_FCC_OFFSET); +	if (epauseparm->tx_pause) +		regval |= XAE_FCC_FCTX_MASK; +	else +		regval &= ~XAE_FCC_FCTX_MASK; +	if (epauseparm->rx_pause) +		regval |= XAE_FCC_FCRX_MASK; +	else +		regval &= ~XAE_FCC_FCRX_MASK; +	axienet_iow(lp, XAE_FCC_OFFSET, regval); + +	return 0; +} + +/** + * axienet_ethtools_get_coalesce - Get DMA interrupt coalescing count. + * @ndev:	Pointer to net_device structure + * @ecoalesce:	Pointer to ethtool_coalesce structure + * + * This implements ethtool command for getting the DMA interrupt coalescing + * count on Tx and Rx paths. Issue "ethtool -c ethX" under linux prompt to + * execute this function. + */ +static int axienet_ethtools_get_coalesce(struct net_device *ndev, +					 struct ethtool_coalesce *ecoalesce) +{ +	u32 regval = 0; +	struct axienet_local *lp = netdev_priv(ndev); +	regval = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); +	ecoalesce->rx_max_coalesced_frames = (regval & XAXIDMA_COALESCE_MASK) +					     >> XAXIDMA_COALESCE_SHIFT; +	regval = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); +	ecoalesce->tx_max_coalesced_frames = (regval & XAXIDMA_COALESCE_MASK) +					     >> XAXIDMA_COALESCE_SHIFT; +	return 0; +} + +/** + * axienet_ethtools_set_coalesce - Set DMA interrupt coalescing count. + * @ndev:	Pointer to net_device structure + * @ecoalesce:	Pointer to ethtool_coalesce structure + * + * This implements ethtool command for setting the DMA interrupt coalescing + * count on Tx and Rx paths. Issue "ethtool -C ethX rx-frames 5" under linux + * prompt to execute this function. + */ +static int axienet_ethtools_set_coalesce(struct net_device *ndev, +					 struct ethtool_coalesce *ecoalesce) +{ +	struct axienet_local *lp = netdev_priv(ndev); + +	if (netif_running(ndev)) { +		printk(KERN_ERR	"%s: Please stop netif before applying " +		       "configruation\n", ndev->name); +		return -EFAULT; +	} + +	if ((ecoalesce->rx_coalesce_usecs) || +	    (ecoalesce->rx_coalesce_usecs_irq) || +	    (ecoalesce->rx_max_coalesced_frames_irq) || +	    (ecoalesce->tx_coalesce_usecs) || +	    (ecoalesce->tx_coalesce_usecs_irq) || +	    (ecoalesce->tx_max_coalesced_frames_irq) || +	    (ecoalesce->stats_block_coalesce_usecs) || +	    (ecoalesce->use_adaptive_rx_coalesce) || +	    (ecoalesce->use_adaptive_tx_coalesce) || +	    (ecoalesce->pkt_rate_low) || +	    (ecoalesce->rx_coalesce_usecs_low) || +	    (ecoalesce->rx_max_coalesced_frames_low) || +	    (ecoalesce->tx_coalesce_usecs_low) || +	    (ecoalesce->tx_max_coalesced_frames_low) || +	    (ecoalesce->pkt_rate_high) || +	    (ecoalesce->rx_coalesce_usecs_high) || +	    (ecoalesce->rx_max_coalesced_frames_high) || +	    (ecoalesce->tx_coalesce_usecs_high) || +	    (ecoalesce->tx_max_coalesced_frames_high) || +	    (ecoalesce->rate_sample_interval)) +		return -EOPNOTSUPP; +	if (ecoalesce->rx_max_coalesced_frames) +		lp->coalesce_count_rx = ecoalesce->rx_max_coalesced_frames; +	if (ecoalesce->tx_max_coalesced_frames) +		lp->coalesce_count_tx = ecoalesce->tx_max_coalesced_frames; + +	return 0; +} + +static struct ethtool_ops axienet_ethtool_ops = { +	.get_settings   = axienet_ethtools_get_settings, +	.set_settings   = axienet_ethtools_set_settings, +	.get_drvinfo    = axienet_ethtools_get_drvinfo, +	.get_regs_len   = axienet_ethtools_get_regs_len, +	.get_regs       = axienet_ethtools_get_regs, +	.get_link       = ethtool_op_get_link, +	.get_pauseparam = axienet_ethtools_get_pauseparam, +	.set_pauseparam = axienet_ethtools_set_pauseparam, +	.get_coalesce   = axienet_ethtools_get_coalesce, +	.set_coalesce   = axienet_ethtools_set_coalesce, +}; + +/** + * axienet_dma_err_handler - Tasklet handler for Axi DMA Error + * @data:	Data passed + * + * Resets the Axi DMA and Axi Ethernet devices, and reconfigures the + * Tx/Rx BDs. + */ +static void axienet_dma_err_handler(unsigned long data) +{ +	u32 axienet_status; +	u32 cr, i; +	int mdio_mcreg; +	struct axienet_local *lp = (struct axienet_local *) data; +	struct net_device *ndev = lp->ndev; +	struct axidma_bd *cur_p; + +	axienet_setoptions(ndev, lp->options & +			   ~(XAE_OPTION_TXEN | XAE_OPTION_RXEN)); +	mdio_mcreg = axienet_ior(lp, XAE_MDIO_MC_OFFSET); +	axienet_mdio_wait_until_ready(lp); +	/* Disable the MDIO interface till Axi Ethernet Reset is completed. +	 * When we do an Axi Ethernet reset, it resets the complete core +	 * including the MDIO. So if MDIO is not disabled when the reset +	 * process is started, MDIO will be broken afterwards. */ +	axienet_iow(lp, XAE_MDIO_MC_OFFSET, (mdio_mcreg & +		    ~XAE_MDIO_MC_MDIOEN_MASK)); + +	__axienet_device_reset(lp, &ndev->dev, XAXIDMA_TX_CR_OFFSET); +	__axienet_device_reset(lp, &ndev->dev, XAXIDMA_RX_CR_OFFSET); + +	axienet_iow(lp, XAE_MDIO_MC_OFFSET, mdio_mcreg); +	axienet_mdio_wait_until_ready(lp); + +	for (i = 0; i < TX_BD_NUM; i++) { +		cur_p = &lp->tx_bd_v[i]; +		if (cur_p->phys) +			dma_unmap_single(ndev->dev.parent, cur_p->phys, +					 (cur_p->cntrl & +					  XAXIDMA_BD_CTRL_LENGTH_MASK), +					 DMA_TO_DEVICE); +		if (cur_p->app4) +			dev_kfree_skb_irq((struct sk_buff *) cur_p->app4); +		cur_p->phys = 0; +		cur_p->cntrl = 0; +		cur_p->status = 0; +		cur_p->app0 = 0; +		cur_p->app1 = 0; +		cur_p->app2 = 0; +		cur_p->app3 = 0; +		cur_p->app4 = 0; +		cur_p->sw_id_offset = 0; +	} + +	for (i = 0; i < RX_BD_NUM; i++) { +		cur_p = &lp->rx_bd_v[i]; +		cur_p->status = 0; +		cur_p->app0 = 0; +		cur_p->app1 = 0; +		cur_p->app2 = 0; +		cur_p->app3 = 0; +		cur_p->app4 = 0; +	} + +	lp->tx_bd_ci = 0; +	lp->tx_bd_tail = 0; +	lp->rx_bd_ci = 0; + +	/* Start updating the Rx channel control register */ +	cr = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); +	/* Update the interrupt coalesce count */ +	cr = ((cr & ~XAXIDMA_COALESCE_MASK) | +	      (XAXIDMA_DFT_RX_THRESHOLD << XAXIDMA_COALESCE_SHIFT)); +	/* Update the delay timer count */ +	cr = ((cr & ~XAXIDMA_DELAY_MASK) | +	      (XAXIDMA_DFT_RX_WAITBOUND << XAXIDMA_DELAY_SHIFT)); +	/* Enable coalesce, delay timer and error interrupts */ +	cr |= XAXIDMA_IRQ_ALL_MASK; +	/* Finally write to the Rx channel control register */ +	axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, cr); + +	/* Start updating the Tx channel control register */ +	cr = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); +	/* Update the interrupt coalesce count */ +	cr = (((cr & ~XAXIDMA_COALESCE_MASK)) | +	      (XAXIDMA_DFT_TX_THRESHOLD << XAXIDMA_COALESCE_SHIFT)); +	/* Update the delay timer count */ +	cr = (((cr & ~XAXIDMA_DELAY_MASK)) | +	      (XAXIDMA_DFT_TX_WAITBOUND << XAXIDMA_DELAY_SHIFT)); +	/* Enable coalesce, delay timer and error interrupts */ +	cr |= XAXIDMA_IRQ_ALL_MASK; +	/* Finally write to the Tx channel control register */ +	axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, cr); + +	/* Populate the tail pointer and bring the Rx Axi DMA engine out of +	 * halted state. This will make the Rx side ready for reception.*/ +	axienet_dma_out32(lp, XAXIDMA_RX_CDESC_OFFSET, lp->rx_bd_p); +	cr = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); +	axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, +			  cr | XAXIDMA_CR_RUNSTOP_MASK); +	axienet_dma_out32(lp, XAXIDMA_RX_TDESC_OFFSET, lp->rx_bd_p + +			  (sizeof(*lp->rx_bd_v) * (RX_BD_NUM - 1))); + +	/* Write to the RS (Run-stop) bit in the Tx channel control register. +	 * Tx channel is now ready to run. But only after we write to the +	 * tail pointer register that the Tx channel will start transmitting */ +	axienet_dma_out32(lp, XAXIDMA_TX_CDESC_OFFSET, lp->tx_bd_p); +	cr = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); +	axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, +			  cr | XAXIDMA_CR_RUNSTOP_MASK); + +	axienet_status = axienet_ior(lp, XAE_RCW1_OFFSET); +	axienet_status &= ~XAE_RCW1_RX_MASK; +	axienet_iow(lp, XAE_RCW1_OFFSET, axienet_status); + +	axienet_status = axienet_ior(lp, XAE_IP_OFFSET); +	if (axienet_status & XAE_INT_RXRJECT_MASK) +		axienet_iow(lp, XAE_IS_OFFSET, XAE_INT_RXRJECT_MASK); +	axienet_iow(lp, XAE_FCC_OFFSET, XAE_FCC_FCRX_MASK); + +	/* Sync default options with HW but leave receiver and +	 * transmitter disabled.*/ +	axienet_setoptions(ndev, lp->options & +			   ~(XAE_OPTION_TXEN | XAE_OPTION_RXEN)); +	axienet_set_mac_address(ndev, NULL); +	axienet_set_multicast_list(ndev); +	axienet_setoptions(ndev, lp->options); +} + +/** + * axienet_of_probe - Axi Ethernet probe function. + * @op:		Pointer to platform device structure. + * @match:	Pointer to device id structure + * + * returns: 0, on success + *	    Non-zero error value on failure. + * + * This is the probe routine for Axi Ethernet driver. This is called before + * any other driver routines are invoked. It allocates and sets up the Ethernet + * device. Parses through device tree and populates fields of + * axienet_local. It registers the Ethernet device. + */ +static int axienet_of_probe(struct platform_device *op) +{ +	__be32 *p; +	int size, ret = 0; +	struct device_node *np; +	struct axienet_local *lp; +	struct net_device *ndev; +	const void *addr; + +	ndev = alloc_etherdev(sizeof(*lp)); +	if (!ndev) +		return -ENOMEM; + +	ether_setup(ndev); +	platform_set_drvdata(op, ndev); + +	SET_NETDEV_DEV(ndev, &op->dev); +	ndev->flags &= ~IFF_MULTICAST;  /* clear multicast */ +	ndev->features = NETIF_F_SG; +	ndev->netdev_ops = &axienet_netdev_ops; +	ndev->ethtool_ops = &axienet_ethtool_ops; + +	lp = netdev_priv(ndev); +	lp->ndev = ndev; +	lp->dev = &op->dev; +	lp->options = XAE_OPTION_DEFAULTS; +	/* Map device registers */ +	lp->regs = of_iomap(op->dev.of_node, 0); +	if (!lp->regs) { +		dev_err(&op->dev, "could not map Axi Ethernet regs.\n"); +		goto nodev; +	} +	/* Setup checksum offload, but default to off if not specified */ +	lp->features = 0; + +	p = (__be32 *) of_get_property(op->dev.of_node, "xlnx,txcsum", NULL); +	if (p) { +		switch (be32_to_cpup(p)) { +		case 1: +			lp->csum_offload_on_tx_path = +				XAE_FEATURE_PARTIAL_TX_CSUM; +			lp->features |= XAE_FEATURE_PARTIAL_TX_CSUM; +			/* Can checksum TCP/UDP over IPv4. */ +			ndev->features |= NETIF_F_IP_CSUM; +			break; +		case 2: +			lp->csum_offload_on_tx_path = +				XAE_FEATURE_FULL_TX_CSUM; +			lp->features |= XAE_FEATURE_FULL_TX_CSUM; +			/* Can checksum TCP/UDP over IPv4. */ +			ndev->features |= NETIF_F_IP_CSUM; +			break; +		default: +			lp->csum_offload_on_tx_path = XAE_NO_CSUM_OFFLOAD; +		} +	} +	p = (__be32 *) of_get_property(op->dev.of_node, "xlnx,rxcsum", NULL); +	if (p) { +		switch (be32_to_cpup(p)) { +		case 1: +			lp->csum_offload_on_rx_path = +				XAE_FEATURE_PARTIAL_RX_CSUM; +			lp->features |= XAE_FEATURE_PARTIAL_RX_CSUM; +			break; +		case 2: +			lp->csum_offload_on_rx_path = +				XAE_FEATURE_FULL_RX_CSUM; +			lp->features |= XAE_FEATURE_FULL_RX_CSUM; +			break; +		default: +			lp->csum_offload_on_rx_path = XAE_NO_CSUM_OFFLOAD; +		} +	} +	/* For supporting jumbo frames, the Axi Ethernet hardware must have +	 * a larger Rx/Tx Memory. Typically, the size must be more than or +	 * equal to 16384 bytes, so that we can enable jumbo option and start +	 * supporting jumbo frames. Here we check for memory allocated for +	 * Rx/Tx in the hardware from the device-tree and accordingly set +	 * flags. */ +	p = (__be32 *) of_get_property(op->dev.of_node, "xlnx,rxmem", NULL); +	if (p) { +		if ((be32_to_cpup(p)) >= 0x4000) +			lp->jumbo_support = 1; +	} +	p = (__be32 *) of_get_property(op->dev.of_node, "xlnx,temac-type", +				       NULL); +	if (p) +		lp->temac_type = be32_to_cpup(p); +	p = (__be32 *) of_get_property(op->dev.of_node, "xlnx,phy-type", NULL); +	if (p) +		lp->phy_type = be32_to_cpup(p); + +	/* Find the DMA node, map the DMA registers, and decode the DMA IRQs */ +	np = of_parse_phandle(op->dev.of_node, "axistream-connected", 0); +	if (!np) { +		dev_err(&op->dev, "could not find DMA node\n"); +		goto err_iounmap; +	} +	lp->dma_regs = of_iomap(np, 0); +	if (lp->dma_regs) { +		dev_dbg(&op->dev, "MEM base: %p\n", lp->dma_regs); +	} else { +		dev_err(&op->dev, "unable to map DMA registers\n"); +		of_node_put(np); +	} +	lp->rx_irq = irq_of_parse_and_map(np, 1); +	lp->tx_irq = irq_of_parse_and_map(np, 0); +	of_node_put(np); +	if ((lp->rx_irq <= 0) || (lp->tx_irq <= 0)) { +		dev_err(&op->dev, "could not determine irqs\n"); +		ret = -ENOMEM; +		goto err_iounmap_2; +	} + +	/* Retrieve the MAC address */ +	addr = of_get_property(op->dev.of_node, "local-mac-address", &size); +	if ((!addr) || (size != 6)) { +		dev_err(&op->dev, "could not find MAC address\n"); +		ret = -ENODEV; +		goto err_iounmap_2; +	} +	axienet_set_mac_address(ndev, (void *) addr); + +	lp->coalesce_count_rx = XAXIDMA_DFT_RX_THRESHOLD; +	lp->coalesce_count_tx = XAXIDMA_DFT_TX_THRESHOLD; + +	lp->phy_node = of_parse_phandle(op->dev.of_node, "phy-handle", 0); +	ret = axienet_mdio_setup(lp, op->dev.of_node); +	if (ret) +		dev_warn(&op->dev, "error registering MDIO bus\n"); + +	ret = register_netdev(lp->ndev); +	if (ret) { +		dev_err(lp->dev, "register_netdev() error (%i)\n", ret); +		goto err_iounmap_2; +	} + +	return 0; + +err_iounmap_2: +	if (lp->dma_regs) +		iounmap(lp->dma_regs); +err_iounmap: +	iounmap(lp->regs); +nodev: +	free_netdev(ndev); +	ndev = NULL; +	return ret; +} + +static int axienet_of_remove(struct platform_device *op) +{ +	struct net_device *ndev = platform_get_drvdata(op); +	struct axienet_local *lp = netdev_priv(ndev); + +	axienet_mdio_teardown(lp); +	unregister_netdev(ndev); + +	if (lp->phy_node) +		of_node_put(lp->phy_node); +	lp->phy_node = NULL; + +	iounmap(lp->regs); +	if (lp->dma_regs) +		iounmap(lp->dma_regs); +	free_netdev(ndev); + +	return 0; +} + +static struct platform_driver axienet_of_driver = { +	.probe = axienet_of_probe, +	.remove = axienet_of_remove, +	.driver = { +		 .owner = THIS_MODULE, +		 .name = "xilinx_axienet", +		 .of_match_table = axienet_of_match, +	}, +}; + +module_platform_driver(axienet_of_driver); + +MODULE_DESCRIPTION("Xilinx Axi Ethernet driver"); +MODULE_AUTHOR("Xilinx"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c new file mode 100644 index 00000000000..d4abf478e2b --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c @@ -0,0 +1,239 @@ +/* + * MDIO bus driver for the Xilinx Axi Ethernet device + * + * Copyright (c) 2009 Secret Lab Technologies, Ltd. + * Copyright (c) 2010 - 2011 Michal Simek <monstr@monstr.eu> + * Copyright (c) 2010 - 2011 PetaLogix + * Copyright (c) 2010 - 2012 Xilinx, Inc. All rights reserved. + */ + +#include <linux/of_address.h> +#include <linux/of_mdio.h> +#include <linux/jiffies.h> + +#include "xilinx_axienet.h" + +#define MAX_MDIO_FREQ		2500000 /* 2.5 MHz */ +#define DEFAULT_CLOCK_DIVISOR	XAE_MDIO_DIV_DFT + +/* Wait till MDIO interface is ready to accept a new transaction.*/ +int axienet_mdio_wait_until_ready(struct axienet_local *lp) +{ +	long end = jiffies + 2; +	while (!(axienet_ior(lp, XAE_MDIO_MCR_OFFSET) & +		 XAE_MDIO_MCR_READY_MASK)) { +		if (time_before_eq(end, jiffies)) { +			WARN_ON(1); +			return -ETIMEDOUT; +		} +		udelay(1); +	} +	return 0; +} + +/** + * axienet_mdio_read - MDIO interface read function + * @bus:	Pointer to mii bus structure + * @phy_id:	Address of the PHY device + * @reg:	PHY register to read + * + * returns:	The register contents on success, -ETIMEDOUT on a timeout + * + * Reads the contents of the requested register from the requested PHY + * address by first writing the details into MCR register. After a while + * the register MRD is read to obtain the PHY register content. + */ +static int axienet_mdio_read(struct mii_bus *bus, int phy_id, int reg) +{ +	u32 rc; +	int ret; +	struct axienet_local *lp = bus->priv; + +	ret = axienet_mdio_wait_until_ready(lp); +	if (ret < 0) +		return ret; + +	axienet_iow(lp, XAE_MDIO_MCR_OFFSET, +		    (((phy_id << XAE_MDIO_MCR_PHYAD_SHIFT) & +		      XAE_MDIO_MCR_PHYAD_MASK) | +		     ((reg << XAE_MDIO_MCR_REGAD_SHIFT) & +		      XAE_MDIO_MCR_REGAD_MASK) | +		     XAE_MDIO_MCR_INITIATE_MASK | +		     XAE_MDIO_MCR_OP_READ_MASK)); + +	ret = axienet_mdio_wait_until_ready(lp); +	if (ret < 0) +		return ret; + +	rc = axienet_ior(lp, XAE_MDIO_MRD_OFFSET) & 0x0000FFFF; + +	dev_dbg(lp->dev, "axienet_mdio_read(phy_id=%i, reg=%x) == %x\n", +		phy_id, reg, rc); + +	return rc; +} + +/** + * axienet_mdio_write - MDIO interface write function + * @bus:	Pointer to mii bus structure + * @phy_id:	Address of the PHY device + * @reg:	PHY register to write to + * @val:	Value to be written into the register + * + * returns:	0 on success, -ETIMEDOUT on a timeout + * + * Writes the value to the requested register by first writing the value + * into MWD register. The the MCR register is then appropriately setup + * to finish the write operation. + */ +static int axienet_mdio_write(struct mii_bus *bus, int phy_id, int reg, +			      u16 val) +{ +	int ret; +	struct axienet_local *lp = bus->priv; + +	dev_dbg(lp->dev, "axienet_mdio_write(phy_id=%i, reg=%x, val=%x)\n", +		phy_id, reg, val); + +	ret = axienet_mdio_wait_until_ready(lp); +	if (ret < 0) +		return ret; + +	axienet_iow(lp, XAE_MDIO_MWD_OFFSET, (u32) val); +	axienet_iow(lp, XAE_MDIO_MCR_OFFSET, +		    (((phy_id << XAE_MDIO_MCR_PHYAD_SHIFT) & +		      XAE_MDIO_MCR_PHYAD_MASK) | +		     ((reg << XAE_MDIO_MCR_REGAD_SHIFT) & +		      XAE_MDIO_MCR_REGAD_MASK) | +		     XAE_MDIO_MCR_INITIATE_MASK | +		     XAE_MDIO_MCR_OP_WRITE_MASK)); + +	ret = axienet_mdio_wait_until_ready(lp); +	if (ret < 0) +		return ret; +	return 0; +} + +/** + * axienet_mdio_setup - MDIO setup function + * @lp:		Pointer to axienet local data structure. + * @np:		Pointer to device node + * + * returns:	0 on success, -ETIMEDOUT on a timeout, -ENOMEM when + *		mdiobus_alloc (to allocate memory for mii bus structure) fails. + * + * Sets up the MDIO interface by initializing the MDIO clock and enabling the + * MDIO interface in hardware. Register the MDIO interface. + **/ +int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np) +{ +	int ret; +	u32 clk_div, host_clock; +	u32 *property_p; +	struct mii_bus *bus; +	struct resource res; +	struct device_node *np1; + +	/* clk_div can be calculated by deriving it from the equation: +	 * fMDIO = fHOST / ((1 + clk_div) * 2) +	 * +	 * Where fMDIO <= 2500000, so we get: +	 * fHOST / ((1 + clk_div) * 2) <= 2500000 +	 * +	 * Then we get: +	 * 1 / ((1 + clk_div) * 2) <= (2500000 / fHOST) +	 * +	 * Then we get: +	 * 1 / (1 + clk_div) <= ((2500000 * 2) / fHOST) +	 * +	 * Then we get: +	 * 1 / (1 + clk_div) <= (5000000 / fHOST) +	 * +	 * So: +	 * (1 + clk_div) >= (fHOST / 5000000) +	 * +	 * And finally: +	 * clk_div >= (fHOST / 5000000) - 1 +	 * +	 * fHOST can be read from the flattened device tree as property +	 * "clock-frequency" from the CPU +	 */ + +	np1 = of_find_node_by_name(NULL, "cpu"); +	if (!np1) { +		printk(KERN_WARNING "%s(): Could not find CPU device node.", +		       __func__); +		printk(KERN_WARNING "Setting MDIO clock divisor to " +		       "default %d\n", DEFAULT_CLOCK_DIVISOR); +		clk_div = DEFAULT_CLOCK_DIVISOR; +		goto issue; +	} +	property_p = (u32 *) of_get_property(np1, "clock-frequency", NULL); +	if (!property_p) { +		printk(KERN_WARNING "%s(): Could not find CPU property: " +		       "clock-frequency.", __func__); +		printk(KERN_WARNING "Setting MDIO clock divisor to " +		       "default %d\n", DEFAULT_CLOCK_DIVISOR); +		clk_div = DEFAULT_CLOCK_DIVISOR; +		of_node_put(np1); +		goto issue; +	} + +	host_clock = be32_to_cpup(property_p); +	clk_div = (host_clock / (MAX_MDIO_FREQ * 2)) - 1; +	/* If there is any remainder from the division of +	 * fHOST / (MAX_MDIO_FREQ * 2), then we need to add +	 * 1 to the clock divisor or we will surely be above 2.5 MHz */ +	if (host_clock % (MAX_MDIO_FREQ * 2)) +		clk_div++; + +	printk(KERN_DEBUG "%s(): Setting MDIO clock divisor to %u based " +	       "on %u Hz host clock.\n", __func__, clk_div, host_clock); + +	of_node_put(np1); +issue: +	axienet_iow(lp, XAE_MDIO_MC_OFFSET, +		    (((u32) clk_div) | XAE_MDIO_MC_MDIOEN_MASK)); + +	ret = axienet_mdio_wait_until_ready(lp); +	if (ret < 0) +		return ret; + +	bus = mdiobus_alloc(); +	if (!bus) +		return -ENOMEM; + +	np1 = of_get_parent(lp->phy_node); +	of_address_to_resource(np1, 0, &res); +	snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx", +		 (unsigned long long) res.start); + +	bus->priv = lp; +	bus->name = "Xilinx Axi Ethernet MDIO"; +	bus->read = axienet_mdio_read; +	bus->write = axienet_mdio_write; +	bus->parent = lp->dev; +	bus->irq = lp->mdio_irqs; /* preallocated IRQ table */ +	lp->mii_bus = bus; + +	ret = of_mdiobus_register(bus, np1); +	if (ret) { +		mdiobus_free(bus); +		return ret; +	} +	return 0; +} + +/** + * axienet_mdio_teardown - MDIO remove function + * @lp:		Pointer to axienet local data structure. + * + * Unregisters the MDIO and frees any associate memory for mii bus. + */ +void axienet_mdio_teardown(struct axienet_local *lp) +{ +	mdiobus_unregister(lp->mii_bus); +	kfree(lp->mii_bus->irq); +	mdiobus_free(lp->mii_bus); +	lp->mii_bus = NULL; +} diff --git a/drivers/net/ethernet/xilinx/xilinx_emaclite.c b/drivers/net/ethernet/xilinx/xilinx_emaclite.c new file mode 100644 index 00000000000..8c4aed3053e --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_emaclite.c @@ -0,0 +1,1259 @@ +/* + * Xilinx EmacLite Linux driver for the Xilinx Ethernet MAC Lite device. + * + * This is a new flat driver which is based on the original emac_lite + * driver from John Williams <john.williams@xilinx.com>. + * + * 2007 - 2013 (c) Xilinx, Inc. + * + * 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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/of_mdio.h> +#include <linux/of_net.h> +#include <linux/phy.h> +#include <linux/interrupt.h> + +#define DRIVER_NAME "xilinx_emaclite" + +/* Register offsets for the EmacLite Core */ +#define XEL_TXBUFF_OFFSET	0x0		/* Transmit Buffer */ +#define XEL_MDIOADDR_OFFSET	0x07E4		/* MDIO Address Register */ +#define XEL_MDIOWR_OFFSET	0x07E8		/* MDIO Write Data Register */ +#define XEL_MDIORD_OFFSET	0x07EC		/* MDIO Read Data Register */ +#define XEL_MDIOCTRL_OFFSET	0x07F0		/* MDIO Control Register */ +#define XEL_GIER_OFFSET		0x07F8		/* GIE Register */ +#define XEL_TSR_OFFSET		0x07FC		/* Tx status */ +#define XEL_TPLR_OFFSET		0x07F4		/* Tx packet length */ + +#define XEL_RXBUFF_OFFSET	0x1000		/* Receive Buffer */ +#define XEL_RPLR_OFFSET		0x100C		/* Rx packet length */ +#define XEL_RSR_OFFSET		0x17FC		/* Rx status */ + +#define XEL_BUFFER_OFFSET	0x0800		/* Next Tx/Rx buffer's offset */ + +/* MDIO Address Register Bit Masks */ +#define XEL_MDIOADDR_REGADR_MASK  0x0000001F	/* Register Address */ +#define XEL_MDIOADDR_PHYADR_MASK  0x000003E0	/* PHY Address */ +#define XEL_MDIOADDR_PHYADR_SHIFT 5 +#define XEL_MDIOADDR_OP_MASK	  0x00000400	/* RD/WR Operation */ + +/* MDIO Write Data Register Bit Masks */ +#define XEL_MDIOWR_WRDATA_MASK	  0x0000FFFF	/* Data to be Written */ + +/* MDIO Read Data Register Bit Masks */ +#define XEL_MDIORD_RDDATA_MASK	  0x0000FFFF	/* Data to be Read */ + +/* MDIO Control Register Bit Masks */ +#define XEL_MDIOCTRL_MDIOSTS_MASK 0x00000001	/* MDIO Status Mask */ +#define XEL_MDIOCTRL_MDIOEN_MASK  0x00000008	/* MDIO Enable */ + +/* Global Interrupt Enable Register (GIER) Bit Masks */ +#define XEL_GIER_GIE_MASK	0x80000000	/* Global Enable */ + +/* Transmit Status Register (TSR) Bit Masks */ +#define XEL_TSR_XMIT_BUSY_MASK	 0x00000001	/* Tx complete */ +#define XEL_TSR_PROGRAM_MASK	 0x00000002	/* Program the MAC address */ +#define XEL_TSR_XMIT_IE_MASK	 0x00000008	/* Tx interrupt enable bit */ +#define XEL_TSR_XMIT_ACTIVE_MASK 0x80000000	/* Buffer is active, SW bit +						 * only. This is not documented +						 * in the HW spec */ + +/* Define for programming the MAC address into the EmacLite */ +#define XEL_TSR_PROG_MAC_ADDR	(XEL_TSR_XMIT_BUSY_MASK | XEL_TSR_PROGRAM_MASK) + +/* Receive Status Register (RSR) */ +#define XEL_RSR_RECV_DONE_MASK	0x00000001	/* Rx complete */ +#define XEL_RSR_RECV_IE_MASK	0x00000008	/* Rx interrupt enable bit */ + +/* Transmit Packet Length Register (TPLR) */ +#define XEL_TPLR_LENGTH_MASK	0x0000FFFF	/* Tx packet length */ + +/* Receive Packet Length Register (RPLR) */ +#define XEL_RPLR_LENGTH_MASK	0x0000FFFF	/* Rx packet length */ + +#define XEL_HEADER_OFFSET	12		/* Offset to length field */ +#define XEL_HEADER_SHIFT	16		/* Shift value for length */ + +/* General Ethernet Definitions */ +#define XEL_ARP_PACKET_SIZE		28	/* Max ARP packet size */ +#define XEL_HEADER_IP_LENGTH_OFFSET	16	/* IP Length Offset */ + + + +#define TX_TIMEOUT		(60*HZ)		/* Tx timeout is 60 seconds. */ +#define ALIGNMENT		4 + +/* BUFFER_ALIGN(adr) calculates the number of bytes to the next alignment. */ +#define BUFFER_ALIGN(adr) ((ALIGNMENT - ((u32) adr)) % ALIGNMENT) + +/** + * struct net_local - Our private per device data + * @ndev:		instance of the network device + * @tx_ping_pong:	indicates whether Tx Pong buffer is configured in HW + * @rx_ping_pong:	indicates whether Rx Pong buffer is configured in HW + * @next_tx_buf_to_use:	next Tx buffer to write to + * @next_rx_buf_to_use:	next Rx buffer to read from + * @base_addr:		base address of the Emaclite device + * @reset_lock:		lock used for synchronization + * @deferred_skb:	holds an skb (for transmission at a later time) when the + *			Tx buffer is not free + * @phy_dev:		pointer to the PHY device + * @phy_node:		pointer to the PHY device node + * @mii_bus:		pointer to the MII bus + * @mdio_irqs:		IRQs table for MDIO bus + * @last_link:		last link status + * @has_mdio:		indicates whether MDIO is included in the HW + */ +struct net_local { + +	struct net_device *ndev; + +	bool tx_ping_pong; +	bool rx_ping_pong; +	u32 next_tx_buf_to_use; +	u32 next_rx_buf_to_use; +	void __iomem *base_addr; + +	spinlock_t reset_lock; +	struct sk_buff *deferred_skb; + +	struct phy_device *phy_dev; +	struct device_node *phy_node; + +	struct mii_bus *mii_bus; +	int mdio_irqs[PHY_MAX_ADDR]; + +	int last_link; +	bool has_mdio; +}; + + +/*************************/ +/* EmacLite driver calls */ +/*************************/ + +/** + * xemaclite_enable_interrupts - Enable the interrupts for the EmacLite device + * @drvdata:	Pointer to the Emaclite device private data + * + * This function enables the Tx and Rx interrupts for the Emaclite device along + * with the Global Interrupt Enable. + */ +static void xemaclite_enable_interrupts(struct net_local *drvdata) +{ +	u32 reg_data; + +	/* Enable the Tx interrupts for the first Buffer */ +	reg_data = __raw_readl(drvdata->base_addr + XEL_TSR_OFFSET); +	__raw_writel(reg_data | XEL_TSR_XMIT_IE_MASK, +		     drvdata->base_addr + XEL_TSR_OFFSET); + +	/* Enable the Rx interrupts for the first buffer */ +	__raw_writel(XEL_RSR_RECV_IE_MASK, drvdata->base_addr + XEL_RSR_OFFSET); + +	/* Enable the Global Interrupt Enable */ +	__raw_writel(XEL_GIER_GIE_MASK, drvdata->base_addr + XEL_GIER_OFFSET); +} + +/** + * xemaclite_disable_interrupts - Disable the interrupts for the EmacLite device + * @drvdata:	Pointer to the Emaclite device private data + * + * This function disables the Tx and Rx interrupts for the Emaclite device, + * along with the Global Interrupt Enable. + */ +static void xemaclite_disable_interrupts(struct net_local *drvdata) +{ +	u32 reg_data; + +	/* Disable the Global Interrupt Enable */ +	__raw_writel(XEL_GIER_GIE_MASK, drvdata->base_addr + XEL_GIER_OFFSET); + +	/* Disable the Tx interrupts for the first buffer */ +	reg_data = __raw_readl(drvdata->base_addr + XEL_TSR_OFFSET); +	__raw_writel(reg_data & (~XEL_TSR_XMIT_IE_MASK), +		     drvdata->base_addr + XEL_TSR_OFFSET); + +	/* Disable the Rx interrupts for the first buffer */ +	reg_data = __raw_readl(drvdata->base_addr + XEL_RSR_OFFSET); +	__raw_writel(reg_data & (~XEL_RSR_RECV_IE_MASK), +		     drvdata->base_addr + XEL_RSR_OFFSET); +} + +/** + * xemaclite_aligned_write - Write from 16-bit aligned to 32-bit aligned address + * @src_ptr:	Void pointer to the 16-bit aligned source address + * @dest_ptr:	Pointer to the 32-bit aligned destination address + * @length:	Number bytes to write from source to destination + * + * This function writes data from a 16-bit aligned buffer to a 32-bit aligned + * address in the EmacLite device. + */ +static void xemaclite_aligned_write(void *src_ptr, u32 *dest_ptr, +				    unsigned length) +{ +	u32 align_buffer; +	u32 *to_u32_ptr; +	u16 *from_u16_ptr, *to_u16_ptr; + +	to_u32_ptr = dest_ptr; +	from_u16_ptr = src_ptr; +	align_buffer = 0; + +	for (; length > 3; length -= 4) { +		to_u16_ptr = (u16 *)&align_buffer; +		*to_u16_ptr++ = *from_u16_ptr++; +		*to_u16_ptr++ = *from_u16_ptr++; + +		/* This barrier resolves occasional issues seen around +		 * cases where the data is not properly flushed out +		 * from the processor store buffers to the destination +		 * memory locations. +		 */ +		wmb(); + +		/* Output a word */ +		*to_u32_ptr++ = align_buffer; +	} +	if (length) { +		u8 *from_u8_ptr, *to_u8_ptr; + +		/* Set up to output the remaining data */ +		align_buffer = 0; +		to_u8_ptr = (u8 *) &align_buffer; +		from_u8_ptr = (u8 *) from_u16_ptr; + +		/* Output the remaining data */ +		for (; length > 0; length--) +			*to_u8_ptr++ = *from_u8_ptr++; + +		/* This barrier resolves occasional issues seen around +		 * cases where the data is not properly flushed out +		 * from the processor store buffers to the destination +		 * memory locations. +		 */ +		wmb(); +		*to_u32_ptr = align_buffer; +	} +} + +/** + * xemaclite_aligned_read - Read from 32-bit aligned to 16-bit aligned buffer + * @src_ptr:	Pointer to the 32-bit aligned source address + * @dest_ptr:	Pointer to the 16-bit aligned destination address + * @length:	Number bytes to read from source to destination + * + * This function reads data from a 32-bit aligned address in the EmacLite device + * to a 16-bit aligned buffer. + */ +static void xemaclite_aligned_read(u32 *src_ptr, u8 *dest_ptr, +				   unsigned length) +{ +	u16 *to_u16_ptr, *from_u16_ptr; +	u32 *from_u32_ptr; +	u32 align_buffer; + +	from_u32_ptr = src_ptr; +	to_u16_ptr = (u16 *) dest_ptr; + +	for (; length > 3; length -= 4) { +		/* Copy each word into the temporary buffer */ +		align_buffer = *from_u32_ptr++; +		from_u16_ptr = (u16 *)&align_buffer; + +		/* Read data from source */ +		*to_u16_ptr++ = *from_u16_ptr++; +		*to_u16_ptr++ = *from_u16_ptr++; +	} + +	if (length) { +		u8 *to_u8_ptr, *from_u8_ptr; + +		/* Set up to read the remaining data */ +		to_u8_ptr = (u8 *) to_u16_ptr; +		align_buffer = *from_u32_ptr++; +		from_u8_ptr = (u8 *) &align_buffer; + +		/* Read the remaining data */ +		for (; length > 0; length--) +			*to_u8_ptr = *from_u8_ptr; +	} +} + +/** + * xemaclite_send_data - Send an Ethernet frame + * @drvdata:	Pointer to the Emaclite device private data + * @data:	Pointer to the data to be sent + * @byte_count:	Total frame size, including header + * + * This function checks if the Tx buffer of the Emaclite device is free to send + * data. If so, it fills the Tx buffer with data for transmission. Otherwise, it + * returns an error. + * + * Return:	0 upon success or -1 if the buffer(s) are full. + * + * Note:	The maximum Tx packet size can not be more than Ethernet header + *		(14 Bytes) + Maximum MTU (1500 bytes). This is excluding FCS. + */ +static int xemaclite_send_data(struct net_local *drvdata, u8 *data, +			       unsigned int byte_count) +{ +	u32 reg_data; +	void __iomem *addr; + +	/* Determine the expected Tx buffer address */ +	addr = drvdata->base_addr + drvdata->next_tx_buf_to_use; + +	/* If the length is too large, truncate it */ +	if (byte_count > ETH_FRAME_LEN) +		byte_count = ETH_FRAME_LEN; + +	/* Check if the expected buffer is available */ +	reg_data = __raw_readl(addr + XEL_TSR_OFFSET); +	if ((reg_data & (XEL_TSR_XMIT_BUSY_MASK | +	     XEL_TSR_XMIT_ACTIVE_MASK)) == 0) { + +		/* Switch to next buffer if configured */ +		if (drvdata->tx_ping_pong != 0) +			drvdata->next_tx_buf_to_use ^= XEL_BUFFER_OFFSET; +	} else if (drvdata->tx_ping_pong != 0) { +		/* If the expected buffer is full, try the other buffer, +		 * if it is configured in HW */ + +		addr = (void __iomem __force *)((u32 __force)addr ^ +						 XEL_BUFFER_OFFSET); +		reg_data = __raw_readl(addr + XEL_TSR_OFFSET); + +		if ((reg_data & (XEL_TSR_XMIT_BUSY_MASK | +		     XEL_TSR_XMIT_ACTIVE_MASK)) != 0) +			return -1; /* Buffers were full, return failure */ +	} else +		return -1; /* Buffer was full, return failure */ + +	/* Write the frame to the buffer */ +	xemaclite_aligned_write(data, (u32 __force *) addr, byte_count); + +	__raw_writel((byte_count & XEL_TPLR_LENGTH_MASK), +		     addr + XEL_TPLR_OFFSET); + +	/* Update the Tx Status Register to indicate that there is a +	 * frame to send. Set the XEL_TSR_XMIT_ACTIVE_MASK flag which +	 * is used by the interrupt handler to check whether a frame +	 * has been transmitted */ +	reg_data = __raw_readl(addr + XEL_TSR_OFFSET); +	reg_data |= (XEL_TSR_XMIT_BUSY_MASK | XEL_TSR_XMIT_ACTIVE_MASK); +	__raw_writel(reg_data, addr + XEL_TSR_OFFSET); + +	return 0; +} + +/** + * xemaclite_recv_data - Receive a frame + * @drvdata:	Pointer to the Emaclite device private data + * @data:	Address where the data is to be received + * + * This function is intended to be called from the interrupt context or + * with a wrapper which waits for the receive frame to be available. + * + * Return:	Total number of bytes received + */ +static u16 xemaclite_recv_data(struct net_local *drvdata, u8 *data) +{ +	void __iomem *addr; +	u16 length, proto_type; +	u32 reg_data; + +	/* Determine the expected buffer address */ +	addr = (drvdata->base_addr + drvdata->next_rx_buf_to_use); + +	/* Verify which buffer has valid data */ +	reg_data = __raw_readl(addr + XEL_RSR_OFFSET); + +	if ((reg_data & XEL_RSR_RECV_DONE_MASK) == XEL_RSR_RECV_DONE_MASK) { +		if (drvdata->rx_ping_pong != 0) +			drvdata->next_rx_buf_to_use ^= XEL_BUFFER_OFFSET; +	} else { +		/* The instance is out of sync, try other buffer if other +		 * buffer is configured, return 0 otherwise. If the instance is +		 * out of sync, do not update the 'next_rx_buf_to_use' since it +		 * will correct on subsequent calls */ +		if (drvdata->rx_ping_pong != 0) +			addr = (void __iomem __force *)((u32 __force)addr ^ +							 XEL_BUFFER_OFFSET); +		else +			return 0;	/* No data was available */ + +		/* Verify that buffer has valid data */ +		reg_data = __raw_readl(addr + XEL_RSR_OFFSET); +		if ((reg_data & XEL_RSR_RECV_DONE_MASK) != +		     XEL_RSR_RECV_DONE_MASK) +			return 0;	/* No data was available */ +	} + +	/* Get the protocol type of the ethernet frame that arrived */ +	proto_type = ((ntohl(__raw_readl(addr + XEL_HEADER_OFFSET + +			XEL_RXBUFF_OFFSET)) >> XEL_HEADER_SHIFT) & +			XEL_RPLR_LENGTH_MASK); + +	/* Check if received ethernet frame is a raw ethernet frame +	 * or an IP packet or an ARP packet */ +	if (proto_type > (ETH_FRAME_LEN + ETH_FCS_LEN)) { + +		if (proto_type == ETH_P_IP) { +			length = ((ntohl(__raw_readl(addr + +					XEL_HEADER_IP_LENGTH_OFFSET + +					XEL_RXBUFF_OFFSET)) >> +					XEL_HEADER_SHIFT) & +					XEL_RPLR_LENGTH_MASK); +			length += ETH_HLEN + ETH_FCS_LEN; + +		} else if (proto_type == ETH_P_ARP) +			length = XEL_ARP_PACKET_SIZE + ETH_HLEN + ETH_FCS_LEN; +		else +			/* Field contains type other than IP or ARP, use max +			 * frame size and let user parse it */ +			length = ETH_FRAME_LEN + ETH_FCS_LEN; +	} else +		/* Use the length in the frame, plus the header and trailer */ +		length = proto_type + ETH_HLEN + ETH_FCS_LEN; + +	/* Read from the EmacLite device */ +	xemaclite_aligned_read((u32 __force *) (addr + XEL_RXBUFF_OFFSET), +				data, length); + +	/* Acknowledge the frame */ +	reg_data = __raw_readl(addr + XEL_RSR_OFFSET); +	reg_data &= ~XEL_RSR_RECV_DONE_MASK; +	__raw_writel(reg_data, addr + XEL_RSR_OFFSET); + +	return length; +} + +/** + * xemaclite_update_address - Update the MAC address in the device + * @drvdata:	Pointer to the Emaclite device private data + * @address_ptr:Pointer to the MAC address (MAC address is a 48-bit value) + * + * Tx must be idle and Rx should be idle for deterministic results. + * It is recommended that this function should be called after the + * initialization and before transmission of any packets from the device. + * The MAC address can be programmed using any of the two transmit + * buffers (if configured). + */ +static void xemaclite_update_address(struct net_local *drvdata, +				     u8 *address_ptr) +{ +	void __iomem *addr; +	u32 reg_data; + +	/* Determine the expected Tx buffer address */ +	addr = drvdata->base_addr + drvdata->next_tx_buf_to_use; + +	xemaclite_aligned_write(address_ptr, (u32 __force *) addr, ETH_ALEN); + +	__raw_writel(ETH_ALEN, addr + XEL_TPLR_OFFSET); + +	/* Update the MAC address in the EmacLite */ +	reg_data = __raw_readl(addr + XEL_TSR_OFFSET); +	__raw_writel(reg_data | XEL_TSR_PROG_MAC_ADDR, addr + XEL_TSR_OFFSET); + +	/* Wait for EmacLite to finish with the MAC address update */ +	while ((__raw_readl(addr + XEL_TSR_OFFSET) & +		XEL_TSR_PROG_MAC_ADDR) != 0) +		; +} + +/** + * xemaclite_set_mac_address - Set the MAC address for this device + * @dev:	Pointer to the network device instance + * @addr:	Void pointer to the sockaddr structure + * + * This function copies the HW address from the sockaddr strucutre to the + * net_device structure and updates the address in HW. + * + * Return:	Error if the net device is busy or 0 if the addr is set + *		successfully + */ +static int xemaclite_set_mac_address(struct net_device *dev, void *address) +{ +	struct net_local *lp = netdev_priv(dev); +	struct sockaddr *addr = address; + +	if (netif_running(dev)) +		return -EBUSY; + +	memcpy(dev->dev_addr, addr->sa_data, dev->addr_len); +	xemaclite_update_address(lp, dev->dev_addr); +	return 0; +} + +/** + * xemaclite_tx_timeout - Callback for Tx Timeout + * @dev:	Pointer to the network device + * + * This function is called when Tx time out occurs for Emaclite device. + */ +static void xemaclite_tx_timeout(struct net_device *dev) +{ +	struct net_local *lp = netdev_priv(dev); +	unsigned long flags; + +	dev_err(&lp->ndev->dev, "Exceeded transmit timeout of %lu ms\n", +		TX_TIMEOUT * 1000UL / HZ); + +	dev->stats.tx_errors++; + +	/* Reset the device */ +	spin_lock_irqsave(&lp->reset_lock, flags); + +	/* Shouldn't really be necessary, but shouldn't hurt */ +	netif_stop_queue(dev); + +	xemaclite_disable_interrupts(lp); +	xemaclite_enable_interrupts(lp); + +	if (lp->deferred_skb) { +		dev_kfree_skb(lp->deferred_skb); +		lp->deferred_skb = NULL; +		dev->stats.tx_errors++; +	} + +	/* To exclude tx timeout */ +	dev->trans_start = jiffies; /* prevent tx timeout */ + +	/* We're all ready to go. Start the queue */ +	netif_wake_queue(dev); +	spin_unlock_irqrestore(&lp->reset_lock, flags); +} + +/**********************/ +/* Interrupt Handlers */ +/**********************/ + +/** + * xemaclite_tx_handler - Interrupt handler for frames sent + * @dev:	Pointer to the network device + * + * This function updates the number of packets transmitted and handles the + * deferred skb, if there is one. + */ +static void xemaclite_tx_handler(struct net_device *dev) +{ +	struct net_local *lp = netdev_priv(dev); + +	dev->stats.tx_packets++; +	if (lp->deferred_skb) { +		if (xemaclite_send_data(lp, +					(u8 *) lp->deferred_skb->data, +					lp->deferred_skb->len) != 0) +			return; +		else { +			dev->stats.tx_bytes += lp->deferred_skb->len; +			dev_kfree_skb_irq(lp->deferred_skb); +			lp->deferred_skb = NULL; +			dev->trans_start = jiffies; /* prevent tx timeout */ +			netif_wake_queue(dev); +		} +	} +} + +/** + * xemaclite_rx_handler- Interrupt handler for frames received + * @dev:	Pointer to the network device + * + * This function allocates memory for a socket buffer, fills it with data + * received and hands it over to the TCP/IP stack. + */ +static void xemaclite_rx_handler(struct net_device *dev) +{ +	struct net_local *lp = netdev_priv(dev); +	struct sk_buff *skb; +	unsigned int align; +	u32 len; + +	len = ETH_FRAME_LEN + ETH_FCS_LEN; +	skb = netdev_alloc_skb(dev, len + ALIGNMENT); +	if (!skb) { +		/* Couldn't get memory. */ +		dev->stats.rx_dropped++; +		dev_err(&lp->ndev->dev, "Could not allocate receive buffer\n"); +		return; +	} + +	/* +	 * A new skb should have the data halfword aligned, but this code is +	 * here just in case that isn't true. Calculate how many +	 * bytes we should reserve to get the data to start on a word +	 * boundary */ +	align = BUFFER_ALIGN(skb->data); +	if (align) +		skb_reserve(skb, align); + +	skb_reserve(skb, 2); + +	len = xemaclite_recv_data(lp, (u8 *) skb->data); + +	if (!len) { +		dev->stats.rx_errors++; +		dev_kfree_skb_irq(skb); +		return; +	} + +	skb_put(skb, len);	/* Tell the skb how much data we got */ + +	skb->protocol = eth_type_trans(skb, dev); +	skb_checksum_none_assert(skb); + +	dev->stats.rx_packets++; +	dev->stats.rx_bytes += len; + +	if (!skb_defer_rx_timestamp(skb)) +		netif_rx(skb);	/* Send the packet upstream */ +} + +/** + * xemaclite_interrupt - Interrupt handler for this driver + * @irq:	Irq of the Emaclite device + * @dev_id:	Void pointer to the network device instance used as callback + *		reference + * + * This function handles the Tx and Rx interrupts of the EmacLite device. + */ +static irqreturn_t xemaclite_interrupt(int irq, void *dev_id) +{ +	bool tx_complete = false; +	struct net_device *dev = dev_id; +	struct net_local *lp = netdev_priv(dev); +	void __iomem *base_addr = lp->base_addr; +	u32 tx_status; + +	/* Check if there is Rx Data available */ +	if ((__raw_readl(base_addr + XEL_RSR_OFFSET) & +			 XEL_RSR_RECV_DONE_MASK) || +	    (__raw_readl(base_addr + XEL_BUFFER_OFFSET + XEL_RSR_OFFSET) +			 & XEL_RSR_RECV_DONE_MASK)) + +		xemaclite_rx_handler(dev); + +	/* Check if the Transmission for the first buffer is completed */ +	tx_status = __raw_readl(base_addr + XEL_TSR_OFFSET); +	if (((tx_status & XEL_TSR_XMIT_BUSY_MASK) == 0) && +		(tx_status & XEL_TSR_XMIT_ACTIVE_MASK) != 0) { + +		tx_status &= ~XEL_TSR_XMIT_ACTIVE_MASK; +		__raw_writel(tx_status, base_addr + XEL_TSR_OFFSET); + +		tx_complete = true; +	} + +	/* Check if the Transmission for the second buffer is completed */ +	tx_status = __raw_readl(base_addr + XEL_BUFFER_OFFSET + XEL_TSR_OFFSET); +	if (((tx_status & XEL_TSR_XMIT_BUSY_MASK) == 0) && +		(tx_status & XEL_TSR_XMIT_ACTIVE_MASK) != 0) { + +		tx_status &= ~XEL_TSR_XMIT_ACTIVE_MASK; +		__raw_writel(tx_status, base_addr + XEL_BUFFER_OFFSET + +			     XEL_TSR_OFFSET); + +		tx_complete = true; +	} + +	/* If there was a Tx interrupt, call the Tx Handler */ +	if (tx_complete != 0) +		xemaclite_tx_handler(dev); + +	return IRQ_HANDLED; +} + +/**********************/ +/* MDIO Bus functions */ +/**********************/ + +/** + * xemaclite_mdio_wait - Wait for the MDIO to be ready to use + * @lp:		Pointer to the Emaclite device private data + * + * This function waits till the device is ready to accept a new MDIO + * request. + * + * Return:	0 for success or ETIMEDOUT for a timeout + */ + +static int xemaclite_mdio_wait(struct net_local *lp) +{ +	long end = jiffies + 2; + +	/* wait for the MDIO interface to not be busy or timeout +	   after some time. +	*/ +	while (__raw_readl(lp->base_addr + XEL_MDIOCTRL_OFFSET) & +			XEL_MDIOCTRL_MDIOSTS_MASK) { +		if (time_before_eq(end, jiffies)) { +			WARN_ON(1); +			return -ETIMEDOUT; +		} +		msleep(1); +	} +	return 0; +} + +/** + * xemaclite_mdio_read - Read from a given MII management register + * @bus:	the mii_bus struct + * @phy_id:	the phy address + * @reg:	register number to read from + * + * This function waits till the device is ready to accept a new MDIO + * request and then writes the phy address to the MDIO Address register + * and reads data from MDIO Read Data register, when its available. + * + * Return:	Value read from the MII management register + */ +static int xemaclite_mdio_read(struct mii_bus *bus, int phy_id, int reg) +{ +	struct net_local *lp = bus->priv; +	u32 ctrl_reg; +	u32 rc; + +	if (xemaclite_mdio_wait(lp)) +		return -ETIMEDOUT; + +	/* Write the PHY address, register number and set the OP bit in the +	 * MDIO Address register. Set the Status bit in the MDIO Control +	 * register to start a MDIO read transaction. +	 */ +	ctrl_reg = __raw_readl(lp->base_addr + XEL_MDIOCTRL_OFFSET); +	__raw_writel(XEL_MDIOADDR_OP_MASK | +		     ((phy_id << XEL_MDIOADDR_PHYADR_SHIFT) | reg), +		     lp->base_addr + XEL_MDIOADDR_OFFSET); +	__raw_writel(ctrl_reg | XEL_MDIOCTRL_MDIOSTS_MASK, +		     lp->base_addr + XEL_MDIOCTRL_OFFSET); + +	if (xemaclite_mdio_wait(lp)) +		return -ETIMEDOUT; + +	rc = __raw_readl(lp->base_addr + XEL_MDIORD_OFFSET); + +	dev_dbg(&lp->ndev->dev, +		"xemaclite_mdio_read(phy_id=%i, reg=%x) == %x\n", +		phy_id, reg, rc); + +	return rc; +} + +/** + * xemaclite_mdio_write - Write to a given MII management register + * @bus:	the mii_bus struct + * @phy_id:	the phy address + * @reg:	register number to write to + * @val:	value to write to the register number specified by reg + * + * This function waits till the device is ready to accept a new MDIO + * request and then writes the val to the MDIO Write Data register. + */ +static int xemaclite_mdio_write(struct mii_bus *bus, int phy_id, int reg, +				u16 val) +{ +	struct net_local *lp = bus->priv; +	u32 ctrl_reg; + +	dev_dbg(&lp->ndev->dev, +		"xemaclite_mdio_write(phy_id=%i, reg=%x, val=%x)\n", +		phy_id, reg, val); + +	if (xemaclite_mdio_wait(lp)) +		return -ETIMEDOUT; + +	/* Write the PHY address, register number and clear the OP bit in the +	 * MDIO Address register and then write the value into the MDIO Write +	 * Data register. Finally, set the Status bit in the MDIO Control +	 * register to start a MDIO write transaction. +	 */ +	ctrl_reg = __raw_readl(lp->base_addr + XEL_MDIOCTRL_OFFSET); +	__raw_writel(~XEL_MDIOADDR_OP_MASK & +		     ((phy_id << XEL_MDIOADDR_PHYADR_SHIFT) | reg), +		     lp->base_addr + XEL_MDIOADDR_OFFSET); +	__raw_writel(val, lp->base_addr + XEL_MDIOWR_OFFSET); +	__raw_writel(ctrl_reg | XEL_MDIOCTRL_MDIOSTS_MASK, +		     lp->base_addr + XEL_MDIOCTRL_OFFSET); + +	return 0; +} + +/** + * xemaclite_mdio_setup - Register mii_bus for the Emaclite device + * @lp:		Pointer to the Emaclite device private data + * @ofdev:	Pointer to OF device structure + * + * This function enables MDIO bus in the Emaclite device and registers a + * mii_bus. + * + * Return:	0 upon success or a negative error upon failure + */ +static int xemaclite_mdio_setup(struct net_local *lp, struct device *dev) +{ +	struct mii_bus *bus; +	int rc; +	struct resource res; +	struct device_node *np = of_get_parent(lp->phy_node); +	struct device_node *npp; + +	/* Don't register the MDIO bus if the phy_node or its parent node +	 * can't be found. +	 */ +	if (!np) { +		dev_err(dev, "Failed to register mdio bus.\n"); +		return -ENODEV; +	} +	npp = of_get_parent(np); + +	of_address_to_resource(npp, 0, &res); +	if (lp->ndev->mem_start != res.start) { +		struct phy_device *phydev; +		phydev = of_phy_find_device(lp->phy_node); +		if (!phydev) +			dev_info(dev, +				 "MDIO of the phy is not registered yet\n"); +		return 0; +	} + +	/* Enable the MDIO bus by asserting the enable bit in MDIO Control +	 * register. +	 */ +	__raw_writel(XEL_MDIOCTRL_MDIOEN_MASK, +		     lp->base_addr + XEL_MDIOCTRL_OFFSET); + +	bus = mdiobus_alloc(); +	if (!bus) { +		dev_err(dev, "Failed to allocate mdiobus\n"); +		return -ENOMEM; +	} + +	snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx", +		 (unsigned long long)res.start); +	bus->priv = lp; +	bus->name = "Xilinx Emaclite MDIO"; +	bus->read = xemaclite_mdio_read; +	bus->write = xemaclite_mdio_write; +	bus->parent = dev; +	bus->irq = lp->mdio_irqs; /* preallocated IRQ table */ + +	lp->mii_bus = bus; + +	rc = of_mdiobus_register(bus, np); +	if (rc) { +		dev_err(dev, "Failed to register mdio bus.\n"); +		goto err_register; +	} + +	return 0; + +err_register: +	mdiobus_free(bus); +	return rc; +} + +/** + * xemaclite_adjust_link - Link state callback for the Emaclite device + * @ndev: pointer to net_device struct + * + * There's nothing in the Emaclite device to be configured when the link + * state changes. We just print the status. + */ +static void xemaclite_adjust_link(struct net_device *ndev) +{ +	struct net_local *lp = netdev_priv(ndev); +	struct phy_device *phy = lp->phy_dev; +	int link_state; + +	/* hash together the state values to decide if something has changed */ +	link_state = phy->speed | (phy->duplex << 1) | phy->link; + +	if (lp->last_link != link_state) { +		lp->last_link = link_state; +		phy_print_status(phy); +	} +} + +/** + * xemaclite_open - Open the network device + * @dev:	Pointer to the network device + * + * This function sets the MAC address, requests an IRQ and enables interrupts + * for the Emaclite device and starts the Tx queue. + * It also connects to the phy device, if MDIO is included in Emaclite device. + */ +static int xemaclite_open(struct net_device *dev) +{ +	struct net_local *lp = netdev_priv(dev); +	int retval; + +	/* Just to be safe, stop the device first */ +	xemaclite_disable_interrupts(lp); + +	if (lp->phy_node) { +		u32 bmcr; + +		lp->phy_dev = of_phy_connect(lp->ndev, lp->phy_node, +					     xemaclite_adjust_link, 0, +					     PHY_INTERFACE_MODE_MII); +		if (!lp->phy_dev) { +			dev_err(&lp->ndev->dev, "of_phy_connect() failed\n"); +			return -ENODEV; +		} + +		/* EmacLite doesn't support giga-bit speeds */ +		lp->phy_dev->supported &= (PHY_BASIC_FEATURES); +		lp->phy_dev->advertising = lp->phy_dev->supported; + +		/* Don't advertise 1000BASE-T Full/Half duplex speeds */ +		phy_write(lp->phy_dev, MII_CTRL1000, 0); + +		/* Advertise only 10 and 100mbps full/half duplex speeds */ +		phy_write(lp->phy_dev, MII_ADVERTISE, ADVERTISE_ALL | +			  ADVERTISE_CSMA); + +		/* Restart auto negotiation */ +		bmcr = phy_read(lp->phy_dev, MII_BMCR); +		bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART); +		phy_write(lp->phy_dev, MII_BMCR, bmcr); + +		phy_start(lp->phy_dev); +	} + +	/* Set the MAC address each time opened */ +	xemaclite_update_address(lp, dev->dev_addr); + +	/* Grab the IRQ */ +	retval = request_irq(dev->irq, xemaclite_interrupt, 0, dev->name, dev); +	if (retval) { +		dev_err(&lp->ndev->dev, "Could not allocate interrupt %d\n", +			dev->irq); +		if (lp->phy_dev) +			phy_disconnect(lp->phy_dev); +		lp->phy_dev = NULL; + +		return retval; +	} + +	/* Enable Interrupts */ +	xemaclite_enable_interrupts(lp); + +	/* We're ready to go */ +	netif_start_queue(dev); + +	return 0; +} + +/** + * xemaclite_close - Close the network device + * @dev:	Pointer to the network device + * + * This function stops the Tx queue, disables interrupts and frees the IRQ for + * the Emaclite device. + * It also disconnects the phy device associated with the Emaclite device. + */ +static int xemaclite_close(struct net_device *dev) +{ +	struct net_local *lp = netdev_priv(dev); + +	netif_stop_queue(dev); +	xemaclite_disable_interrupts(lp); +	free_irq(dev->irq, dev); + +	if (lp->phy_dev) +		phy_disconnect(lp->phy_dev); +	lp->phy_dev = NULL; + +	return 0; +} + +/** + * xemaclite_send - Transmit a frame + * @orig_skb:	Pointer to the socket buffer to be transmitted + * @dev:	Pointer to the network device + * + * This function checks if the Tx buffer of the Emaclite device is free to send + * data. If so, it fills the Tx buffer with data from socket buffer data, + * updates the stats and frees the socket buffer. The Tx completion is signaled + * by an interrupt. If the Tx buffer isn't free, then the socket buffer is + * deferred and the Tx queue is stopped so that the deferred socket buffer can + * be transmitted when the Emaclite device is free to transmit data. + * + * Return:	0, always. + */ +static int xemaclite_send(struct sk_buff *orig_skb, struct net_device *dev) +{ +	struct net_local *lp = netdev_priv(dev); +	struct sk_buff *new_skb; +	unsigned int len; +	unsigned long flags; + +	len = orig_skb->len; + +	new_skb = orig_skb; + +	spin_lock_irqsave(&lp->reset_lock, flags); +	if (xemaclite_send_data(lp, (u8 *) new_skb->data, len) != 0) { +		/* If the Emaclite Tx buffer is busy, stop the Tx queue and +		 * defer the skb for transmission during the ISR, after the +		 * current transmission is complete */ +		netif_stop_queue(dev); +		lp->deferred_skb = new_skb; +		/* Take the time stamp now, since we can't do this in an ISR. */ +		skb_tx_timestamp(new_skb); +		spin_unlock_irqrestore(&lp->reset_lock, flags); +		return 0; +	} +	spin_unlock_irqrestore(&lp->reset_lock, flags); + +	skb_tx_timestamp(new_skb); + +	dev->stats.tx_bytes += len; +	dev_consume_skb_any(new_skb); + +	return 0; +} + +/** + * xemaclite_remove_ndev - Free the network device + * @ndev:	Pointer to the network device to be freed + * + * This function un maps the IO region of the Emaclite device and frees the net + * device. + */ +static void xemaclite_remove_ndev(struct net_device *ndev) +{ +	if (ndev) { +		free_netdev(ndev); +	} +} + +/** + * get_bool - Get a parameter from the OF device + * @ofdev:	Pointer to OF device structure + * @s:		Property to be retrieved + * + * This function looks for a property in the device node and returns the value + * of the property if its found or 0 if the property is not found. + * + * Return:	Value of the parameter if the parameter is found, or 0 otherwise + */ +static bool get_bool(struct platform_device *ofdev, const char *s) +{ +	u32 *p = (u32 *)of_get_property(ofdev->dev.of_node, s, NULL); + +	if (p) { +		return (bool)*p; +	} else { +		dev_warn(&ofdev->dev, "Parameter %s not found," +			"defaulting to false\n", s); +		return 0; +	} +} + +static struct net_device_ops xemaclite_netdev_ops; + +/** + * xemaclite_of_probe - Probe method for the Emaclite device. + * @ofdev:	Pointer to OF device structure + * @match:	Pointer to the structure used for matching a device + * + * This function probes for the Emaclite device in the device tree. + * It initializes the driver data structure and the hardware, sets the MAC + * address and registers the network device. + * It also registers a mii_bus for the Emaclite device, if MDIO is included + * in the device. + * + * Return:	0, if the driver is bound to the Emaclite device, or + *		a negative error if there is failure. + */ +static int xemaclite_of_probe(struct platform_device *ofdev) +{ +	struct resource *res; +	struct net_device *ndev = NULL; +	struct net_local *lp = NULL; +	struct device *dev = &ofdev->dev; +	const void *mac_address; + +	int rc = 0; + +	dev_info(dev, "Device Tree Probing\n"); + +	/* Create an ethernet device instance */ +	ndev = alloc_etherdev(sizeof(struct net_local)); +	if (!ndev) +		return -ENOMEM; + +	dev_set_drvdata(dev, ndev); +	SET_NETDEV_DEV(ndev, &ofdev->dev); + +	lp = netdev_priv(ndev); +	lp->ndev = ndev; + +	/* Get IRQ for the device */ +	res = platform_get_resource(ofdev, IORESOURCE_IRQ, 0); +	if (!res) { +		dev_err(dev, "no IRQ found\n"); +		goto error; +	} + +	ndev->irq = res->start; + +	res = platform_get_resource(ofdev, IORESOURCE_MEM, 0); +	lp->base_addr = devm_ioremap_resource(&ofdev->dev, res); +	if (IS_ERR(lp->base_addr)) { +		rc = PTR_ERR(lp->base_addr); +		goto error; +	} + +	ndev->mem_start = res->start; +	ndev->mem_end = res->end; + +	spin_lock_init(&lp->reset_lock); +	lp->next_tx_buf_to_use = 0x0; +	lp->next_rx_buf_to_use = 0x0; +	lp->tx_ping_pong = get_bool(ofdev, "xlnx,tx-ping-pong"); +	lp->rx_ping_pong = get_bool(ofdev, "xlnx,rx-ping-pong"); +	mac_address = of_get_mac_address(ofdev->dev.of_node); + +	if (mac_address) +		/* Set the MAC address. */ +		memcpy(ndev->dev_addr, mac_address, ETH_ALEN); +	else +		dev_warn(dev, "No MAC address found\n"); + +	/* Clear the Tx CSR's in case this is a restart */ +	__raw_writel(0, lp->base_addr + XEL_TSR_OFFSET); +	__raw_writel(0, lp->base_addr + XEL_BUFFER_OFFSET + XEL_TSR_OFFSET); + +	/* Set the MAC address in the EmacLite device */ +	xemaclite_update_address(lp, ndev->dev_addr); + +	lp->phy_node = of_parse_phandle(ofdev->dev.of_node, "phy-handle", 0); +	rc = xemaclite_mdio_setup(lp, &ofdev->dev); +	if (rc) +		dev_warn(&ofdev->dev, "error registering MDIO bus\n"); + +	dev_info(dev, "MAC address is now %pM\n", ndev->dev_addr); + +	ndev->netdev_ops = &xemaclite_netdev_ops; +	ndev->flags &= ~IFF_MULTICAST; +	ndev->watchdog_timeo = TX_TIMEOUT; + +	/* Finally, register the device */ +	rc = register_netdev(ndev); +	if (rc) { +		dev_err(dev, +			"Cannot register network device, aborting\n"); +		goto error; +	} + +	dev_info(dev, +		 "Xilinx EmacLite at 0x%08X mapped to 0x%08X, irq=%d\n", +		 (unsigned int __force)ndev->mem_start, +		 (unsigned int __force)lp->base_addr, ndev->irq); +	return 0; + +error: +	xemaclite_remove_ndev(ndev); +	return rc; +} + +/** + * xemaclite_of_remove - Unbind the driver from the Emaclite device. + * @of_dev:	Pointer to OF device structure + * + * This function is called if a device is physically removed from the system or + * if the driver module is being unloaded. It frees any resources allocated to + * the device. + * + * Return:	0, always. + */ +static int xemaclite_of_remove(struct platform_device *of_dev) +{ +	struct net_device *ndev = platform_get_drvdata(of_dev); + +	struct net_local *lp = netdev_priv(ndev); + +	/* Un-register the mii_bus, if configured */ +	if (lp->has_mdio) { +		mdiobus_unregister(lp->mii_bus); +		kfree(lp->mii_bus->irq); +		mdiobus_free(lp->mii_bus); +		lp->mii_bus = NULL; +	} + +	unregister_netdev(ndev); + +	if (lp->phy_node) +		of_node_put(lp->phy_node); +	lp->phy_node = NULL; + +	xemaclite_remove_ndev(ndev); + +	return 0; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void +xemaclite_poll_controller(struct net_device *ndev) +{ +	disable_irq(ndev->irq); +	xemaclite_interrupt(ndev->irq, ndev); +	enable_irq(ndev->irq); +} +#endif + +static struct net_device_ops xemaclite_netdev_ops = { +	.ndo_open		= xemaclite_open, +	.ndo_stop		= xemaclite_close, +	.ndo_start_xmit		= xemaclite_send, +	.ndo_set_mac_address	= xemaclite_set_mac_address, +	.ndo_tx_timeout		= xemaclite_tx_timeout, +#ifdef CONFIG_NET_POLL_CONTROLLER +	.ndo_poll_controller = xemaclite_poll_controller, +#endif +}; + +/* Match table for OF platform binding */ +static struct of_device_id xemaclite_of_match[] = { +	{ .compatible = "xlnx,opb-ethernetlite-1.01.a", }, +	{ .compatible = "xlnx,opb-ethernetlite-1.01.b", }, +	{ .compatible = "xlnx,xps-ethernetlite-1.00.a", }, +	{ .compatible = "xlnx,xps-ethernetlite-2.00.a", }, +	{ .compatible = "xlnx,xps-ethernetlite-2.01.a", }, +	{ .compatible = "xlnx,xps-ethernetlite-3.00.a", }, +	{ /* end of list */ }, +}; +MODULE_DEVICE_TABLE(of, xemaclite_of_match); + +static struct platform_driver xemaclite_of_driver = { +	.driver = { +		.name = DRIVER_NAME, +		.owner = THIS_MODULE, +		.of_match_table = xemaclite_of_match, +	}, +	.probe		= xemaclite_of_probe, +	.remove		= xemaclite_of_remove, +}; + +module_platform_driver(xemaclite_of_driver); + +MODULE_AUTHOR("Xilinx, Inc."); +MODULE_DESCRIPTION("Xilinx Ethernet MAC Lite driver"); +MODULE_LICENSE("GPL");  | 
