diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/net/ethernet/xilinx | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | drivers/net/ethernet/xilinx/Kconfig | 42 | ||||
-rw-r--r-- | drivers/net/ethernet/xilinx/Makefile | 10 | ||||
-rw-r--r-- | drivers/net/ethernet/xilinx/ll_temac.h | 414 | ||||
-rw-r--r-- | drivers/net/ethernet/xilinx/ll_temac_main.c | 1667 | ||||
-rw-r--r-- | drivers/net/ethernet/xilinx/ll_temac_mdio.c | 129 | ||||
-rw-r--r-- | drivers/net/ethernet/xilinx/xilinx_axienet.h | 619 | ||||
-rw-r--r-- | drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 2234 | ||||
-rw-r--r-- | drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c | 297 | ||||
-rw-r--r-- | drivers/net/ethernet/xilinx/xilinx_emaclite.c | 1272 |
9 files changed, 6684 insertions, 0 deletions
diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig new file mode 100644 index 000000000..0014729b8 --- /dev/null +++ b/drivers/net/ethernet/xilinx/Kconfig @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Xilinx device configuration +# + +config NET_VENDOR_XILINX + bool "Xilinx devices" + default y + help + If you have a network (Ethernet) card belonging to this class, say Y. + + 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 HAS_IOMEM + 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 HAS_IOMEM + select PHYLINK + help + This driver supports the 10/100/1000 Ethernet from Xilinx for the + AXI bus interface used in Xilinx Virtex FPGAs and Soc's. + +config XILINX_LL_TEMAC + tristate "Xilinx LL TEMAC (LocalLink Tri-mode Ethernet MAC) driver" + depends on HAS_IOMEM + 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 000000000..7d7dc1771 --- /dev/null +++ b/drivers/net/ethernet/xilinx/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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 000000000..6668d1b76 --- /dev/null +++ b/drivers/net/ethernet/xilinx/ll_temac.h @@ -0,0 +1,414 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#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 Management 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) +/* undocumented */ +#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 device_node *phy_node; + /* For non-device-tree devices */ + char phy_name[MII_BUS_ID_SIZE + 3]; + phy_interface_t phy_interface; + + /* MDIO bus data */ + struct mii_bus *mii_bus; /* MII bus reference */ + + /* IO registers, dma functions and IRQs */ + void __iomem *regs; + void __iomem *sdma_regs; +#ifdef CONFIG_PPC_DCR + dcr_host_t sdma_dcrs; +#endif + u32 (*temac_ior)(struct temac_local *lp, int offset); + void (*temac_iow)(struct temac_local *lp, int offset, u32 value); + u32 (*dma_in)(struct temac_local *lp, int reg); + void (*dma_out)(struct temac_local *lp, int reg, u32 value); + + int tx_irq; + int rx_irq; + int emac_num; + + struct sk_buff **rx_skb; + spinlock_t rx_lock; + /* For synchronization of indirect register access. Must be + * shared mutex between interfaces in same TEMAC block. + */ + spinlock_t *indirect_lock; + 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; + u32 tx_bd_num; + struct cdmac_bd *rx_bd_v; + dma_addr_t rx_bd_p; + u32 rx_bd_num; + int tx_bd_ci; + int tx_bd_tail; + int rx_bd_ci; + int rx_bd_tail; + + /* DMA channel control setup */ + u8 coalesce_count_tx; + u8 coalesce_delay_tx; + u8 coalesce_count_rx; + u8 coalesce_delay_rx; + + struct delayed_work restart_work; +}; + +/* Wrappers for temac_ior()/temac_iow() function pointers above */ +#define temac_ior(lp, o) ((lp)->temac_ior(lp, o)) +#define temac_iow(lp, o, v) ((lp)->temac_iow(lp, o, v)) + +/* xilinx_temac.c */ +int temac_indirect_busywait(struct temac_local *lp); +u32 temac_indirect_in32(struct temac_local *lp, int reg); +u32 temac_indirect_in32_locked(struct temac_local *lp, int reg); +void temac_indirect_out32(struct temac_local *lp, int reg, u32 value); +void temac_indirect_out32_locked(struct temac_local *lp, int reg, u32 value); + +/* xilinx_temac_mdio.c */ +int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev); +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 000000000..6bf5e341c --- /dev/null +++ b/drivers/net/ethernet/xilinx/ll_temac_main.c @@ -0,0 +1,1667 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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 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/if_ether.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/of_mdio.h> +#include <linux/of_net.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/workqueue.h> +#include <linux/dma-mapping.h> +#include <linux/processor.h> +#include <linux/platform_data/xilinx-ll-temac.h> + +#include "ll_temac.h" + +/* Descriptors defines for Tx and Rx DMA */ +#define TX_BD_NUM_DEFAULT 64 +#define RX_BD_NUM_DEFAULT 1024 +#define TX_BD_NUM_MAX 4096 +#define RX_BD_NUM_MAX 4096 + +/* --------------------------------------------------------------------- + * Low level register access functions + */ + +static u32 _temac_ior_be(struct temac_local *lp, int offset) +{ + return ioread32be(lp->regs + offset); +} + +static void _temac_iow_be(struct temac_local *lp, int offset, u32 value) +{ + return iowrite32be(value, lp->regs + offset); +} + +static u32 _temac_ior_le(struct temac_local *lp, int offset) +{ + return ioread32(lp->regs + offset); +} + +static void _temac_iow_le(struct temac_local *lp, int offset, u32 value) +{ + return iowrite32(value, lp->regs + offset); +} + +static bool hard_acs_rdy(struct temac_local *lp) +{ + return temac_ior(lp, XTE_RDY0_OFFSET) & XTE_RDY0_HARD_ACS_RDY_MASK; +} + +static bool hard_acs_rdy_or_timeout(struct temac_local *lp, ktime_t timeout) +{ + ktime_t cur = ktime_get(); + + return hard_acs_rdy(lp) || ktime_after(cur, timeout); +} + +/* Poll for maximum 20 ms. This is similar to the 2 jiffies @ 100 Hz + * that was used before, and should cover MDIO bus speed down to 3200 + * Hz. + */ +#define HARD_ACS_RDY_POLL_NS (20 * NSEC_PER_MSEC) + +/* + * temac_indirect_busywait - Wait for current indirect register access + * to complete. + */ +int temac_indirect_busywait(struct temac_local *lp) +{ + ktime_t timeout = ktime_add_ns(ktime_get(), HARD_ACS_RDY_POLL_NS); + + spin_until_cond(hard_acs_rdy_or_timeout(lp, timeout)); + if (WARN_ON(!hard_acs_rdy(lp))) + return -ETIMEDOUT; + + return 0; +} + +/* + * temac_indirect_in32 - Indirect register read access. This function + * must be called without lp->indirect_lock being held. + */ +u32 temac_indirect_in32(struct temac_local *lp, int reg) +{ + unsigned long flags; + int val; + + spin_lock_irqsave(lp->indirect_lock, flags); + val = temac_indirect_in32_locked(lp, reg); + spin_unlock_irqrestore(lp->indirect_lock, flags); + return val; +} + +/* + * temac_indirect_in32_locked - Indirect register read access. This + * function must be called with lp->indirect_lock being held. Use + * this together with spin_lock_irqsave/spin_lock_irqrestore to avoid + * repeated lock/unlock and to ensure uninterrupted access to indirect + * registers. + */ +u32 temac_indirect_in32_locked(struct temac_local *lp, int reg) +{ + /* This initial wait should normally not spin, as we always + * try to wait for indirect access to complete before + * releasing the indirect_lock. + */ + if (WARN_ON(temac_indirect_busywait(lp))) + return -ETIMEDOUT; + /* Initiate read from indirect register */ + temac_iow(lp, XTE_CTL0_OFFSET, reg); + /* Wait for indirect register access to complete. We really + * should not see timeouts, and could even end up causing + * problem for following indirect access, so let's make a bit + * of WARN noise. + */ + if (WARN_ON(temac_indirect_busywait(lp))) + return -ETIMEDOUT; + /* Value is ready now */ + return temac_ior(lp, XTE_LSW0_OFFSET); +} + +/* + * temac_indirect_out32 - Indirect register write access. This function + * must be called without lp->indirect_lock being held. + */ +void temac_indirect_out32(struct temac_local *lp, int reg, u32 value) +{ + unsigned long flags; + + spin_lock_irqsave(lp->indirect_lock, flags); + temac_indirect_out32_locked(lp, reg, value); + spin_unlock_irqrestore(lp->indirect_lock, flags); +} + +/* + * temac_indirect_out32_locked - Indirect register write access. This + * function must be called with lp->indirect_lock being held. Use + * this together with spin_lock_irqsave/spin_lock_irqrestore to avoid + * repeated lock/unlock and to ensure uninterrupted access to indirect + * registers. + */ +void temac_indirect_out32_locked(struct temac_local *lp, int reg, u32 value) +{ + /* As in temac_indirect_in32_locked(), we should normally not + * spin here. And if it happens, we actually end up silently + * ignoring the write request. Ouch. + */ + if (WARN_ON(temac_indirect_busywait(lp))) + return; + /* Initiate write to indirect register */ + temac_iow(lp, XTE_LSW0_OFFSET, value); + temac_iow(lp, XTE_CTL0_OFFSET, CNTLREG_WRITE_ENABLE_MASK | reg); + /* As in temac_indirect_in32_locked(), we should not see timeouts + * here. And if it happens, we continue before the write has + * completed. Not good. + */ + WARN_ON(temac_indirect_busywait(lp)); +} + +/* + * temac_dma_in32_* - Memory mapped DMA read, these function expects a + * register input that is based on DCR word addresses which are then + * converted to memory mapped byte addresses. To be assigned to + * lp->dma_in32. + */ +static u32 temac_dma_in32_be(struct temac_local *lp, int reg) +{ + return ioread32be(lp->sdma_regs + (reg << 2)); +} + +static u32 temac_dma_in32_le(struct temac_local *lp, int reg) +{ + return ioread32(lp->sdma_regs + (reg << 2)); +} + +/* + * temac_dma_out32_* - Memory mapped DMA read, these function expects + * a register input that is based on DCR word addresses which are then + * converted to memory mapped byte addresses. To be assigned to + * lp->dma_out32. + */ +static void temac_dma_out32_be(struct temac_local *lp, int reg, u32 value) +{ + iowrite32be(value, lp->sdma_regs + (reg << 2)); +} + +static void temac_dma_out32_le(struct temac_local *lp, int reg, u32 value) +{ + iowrite32(value, lp->sdma_regs + (reg << 2)); +} + +/* 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 and x86 + */ +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 < lp->rx_bd_num; i++) { + if (!lp->rx_skb[i]) + break; + 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) * lp->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) * lp->tx_bd_num, + lp->tx_bd_v, lp->tx_bd_p); +} + +/* + * 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; + dma_addr_t skb_dma_addr; + int i; + + lp->rx_skb = devm_kcalloc(&ndev->dev, lp->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_alloc_coherent(ndev->dev.parent, + sizeof(*lp->tx_bd_v) * lp->tx_bd_num, + &lp->tx_bd_p, GFP_KERNEL); + if (!lp->tx_bd_v) + goto out; + + lp->rx_bd_v = dma_alloc_coherent(ndev->dev.parent, + sizeof(*lp->rx_bd_v) * lp->rx_bd_num, + &lp->rx_bd_p, GFP_KERNEL); + if (!lp->rx_bd_v) + goto out; + + for (i = 0; i < lp->tx_bd_num; i++) { + lp->tx_bd_v[i].next = cpu_to_be32(lp->tx_bd_p + + sizeof(*lp->tx_bd_v) * ((i + 1) % lp->tx_bd_num)); + } + + for (i = 0; i < lp->rx_bd_num; i++) { + lp->rx_bd_v[i].next = cpu_to_be32(lp->rx_bd_p + + sizeof(*lp->rx_bd_v) * ((i + 1) % lp->rx_bd_num)); + + skb = __netdev_alloc_skb_ip_align(ndev, + XTE_MAX_JUMBO_FRAME_SIZE, + GFP_KERNEL); + if (!skb) + goto out; + + lp->rx_skb[i] = skb; + /* returns physical address of skb->data */ + skb_dma_addr = dma_map_single(ndev->dev.parent, skb->data, + XTE_MAX_JUMBO_FRAME_SIZE, + DMA_FROM_DEVICE); + if (dma_mapping_error(ndev->dev.parent, skb_dma_addr)) + goto out; + lp->rx_bd_v[i].phys = cpu_to_be32(skb_dma_addr); + lp->rx_bd_v[i].len = cpu_to_be32(XTE_MAX_JUMBO_FRAME_SIZE); + lp->rx_bd_v[i].app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND); + } + + /* Configure DMA channel (irq setup) */ + lp->dma_out(lp, TX_CHNL_CTRL, + lp->coalesce_delay_tx << 24 | lp->coalesce_count_tx << 16 | + 0x00000400 | // Use 1 Bit Wide Counters. Currently Not Used! + CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN | + CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN); + lp->dma_out(lp, RX_CHNL_CTRL, + lp->coalesce_delay_rx << 24 | lp->coalesce_count_rx << 16 | + CHNL_CTRL_IRQ_IOE | + CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN | + CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN); + + /* Init descriptor indexes */ + lp->tx_bd_ci = 0; + lp->tx_bd_tail = 0; + lp->rx_bd_ci = 0; + lp->rx_bd_tail = lp->rx_bd_num - 1; + + /* Enable RX DMA transfers */ + wmb(); + 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) * lp->rx_bd_tail)); + + /* Prepare for TX DMA transfer */ + lp->dma_out(lp, TX_CURDESC_PTR, lp->tx_bd_p); + + 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); + unsigned long flags; + + /* set up unicast MAC address filter set its mac address */ + spin_lock_irqsave(lp->indirect_lock, flags); + temac_indirect_out32_locked(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_locked(lp, XTE_UAW1_OFFSET, + (ndev->dev_addr[4] & 0x000000ff) | + (ndev->dev_addr[5] << 8)); + spin_unlock_irqrestore(lp->indirect_lock, flags); +} + +static int temac_init_mac_address(struct net_device *ndev, const void *address) +{ + eth_hw_addr_set(ndev, address); + 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; + eth_hw_addr_set(ndev, addr->sa_data); + 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; + int i = 0; + unsigned long flags; + bool promisc_mode_disabled = false; + + if (ndev->flags & (IFF_PROMISC | IFF_ALLMULTI) || + (netdev_mc_count(ndev) > MULTICAST_CAM_TABLE_NUM)) { + temac_indirect_out32(lp, XTE_AFM_OFFSET, XTE_AFM_EPPRM_MASK); + dev_info(&ndev->dev, "Promiscuous mode enabled.\n"); + return; + } + + spin_lock_irqsave(lp->indirect_lock, flags); + + if (!netdev_mc_empty(ndev)) { + struct netdev_hw_addr *ha; + + netdev_for_each_mc_addr(ha, ndev) { + if (WARN_ON(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_locked(lp, XTE_MAW0_OFFSET, + multi_addr_msw); + multi_addr_lsw = ((ha->addr[5] << 8) | + (ha->addr[4]) | (i << 16)); + temac_indirect_out32_locked(lp, XTE_MAW1_OFFSET, + multi_addr_lsw); + i++; + } + } + + /* Clear all or remaining/unused address table entries */ + while (i < MULTICAST_CAM_TABLE_NUM) { + temac_indirect_out32_locked(lp, XTE_MAW0_OFFSET, 0); + temac_indirect_out32_locked(lp, XTE_MAW1_OFFSET, i << 16); + i++; + } + + /* Enable address filter block if currently disabled */ + if (temac_indirect_in32_locked(lp, XTE_AFM_OFFSET) + & XTE_AFM_EPPRM_MASK) { + temac_indirect_out32_locked(lp, XTE_AFM_OFFSET, 0); + promisc_mode_disabled = true; + } + + spin_unlock_irqrestore(lp->indirect_lock, flags); + + if (promisc_mode_disabled) + dev_info(&ndev->dev, "Promiscuous mode disabled.\n"); +} + +static 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; + unsigned long flags; + + spin_lock_irqsave(lp->indirect_lock, flags); + while (tp->opt) { + reg = temac_indirect_in32_locked(lp, tp->reg) & ~tp->m_or; + if (options & tp->opt) { + reg |= tp->m_or; + temac_indirect_out32_locked(lp, tp->reg, reg); + } + tp++; + } + spin_unlock_irqrestore(lp->indirect_lock, flags); + lp->options |= options; + + return 0; +} + +/* Initialize temac */ +static void temac_device_reset(struct net_device *ndev) +{ + struct temac_local *lp = netdev_priv(ndev); + u32 timeout; + u32 val; + unsigned long flags; + + /* Perform a software reset */ + + /* 0x300 host enable bit ? */ + /* reset PHY through control register ?:1 */ + + dev_dbg(&ndev->dev, "%s()\n", __func__); + + /* 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, + "%s RX reset timeout!!\n", __func__); + 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, + "%s TX reset timeout!!\n", __func__); + break; + } + } + + /* Disable the receiver */ + spin_lock_irqsave(lp->indirect_lock, flags); + val = temac_indirect_in32_locked(lp, XTE_RXC1_OFFSET); + temac_indirect_out32_locked(lp, XTE_RXC1_OFFSET, + val & ~XTE_RXC1_RXEN_MASK); + spin_unlock_irqrestore(lp->indirect_lock, flags); + + /* 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, + "%s DMA reset timeout!!\n", __func__); + break; + } + } + lp->dma_out(lp, DMA_CONTROL_REG, DMA_TAIL_ENABLE); + + if (temac_dma_bd_init(ndev)) { + dev_err(&ndev->dev, + "%s descriptor allocation failed\n", __func__); + } + + spin_lock_irqsave(lp->indirect_lock, flags); + temac_indirect_out32_locked(lp, XTE_RXC0_OFFSET, 0); + temac_indirect_out32_locked(lp, XTE_RXC1_OFFSET, 0); + temac_indirect_out32_locked(lp, XTE_TXC_OFFSET, 0); + temac_indirect_out32_locked(lp, XTE_FCC_OFFSET, XTE_FCC_RXFLO_MASK); + spin_unlock_irqrestore(lp->indirect_lock, flags); + + /* 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 */ + netif_trans_update(ndev); /* prevent tx timeout */ +} + +static void temac_adjust_link(struct net_device *ndev) +{ + struct temac_local *lp = netdev_priv(ndev); + struct phy_device *phy = ndev->phydev; + u32 mii_speed; + int link_state; + unsigned long flags; + + /* 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) { + spin_lock_irqsave(lp->indirect_lock, flags); + mii_speed = temac_indirect_in32_locked(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_locked(lp, XTE_EMCFG_OFFSET, mii_speed); + spin_unlock_irqrestore(lp->indirect_lock, flags); + + lp->last_link = link_state; + phy_print_status(phy); + } +} + +#ifdef CONFIG_64BIT + +static void ptr_to_txbd(void *p, struct cdmac_bd *bd) +{ + bd->app3 = (u32)(((u64)p) >> 32); + bd->app4 = (u32)((u64)p & 0xFFFFFFFF); +} + +static void *ptr_from_txbd(struct cdmac_bd *bd) +{ + return (void *)(((u64)(bd->app3) << 32) | bd->app4); +} + +#else + +static void ptr_to_txbd(void *p, struct cdmac_bd *bd) +{ + bd->app4 = (u32)p; +} + +static void *ptr_from_txbd(struct cdmac_bd *bd) +{ + return (void *)(bd->app4); +} + +#endif + +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; + struct sk_buff *skb; + + cur_p = &lp->tx_bd_v[lp->tx_bd_ci]; + stat = be32_to_cpu(cur_p->app0); + + while (stat & STS_CTRL_APP0_CMPLT) { + /* Make sure that the other fields are read after bd is + * released by dma + */ + rmb(); + dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys), + be32_to_cpu(cur_p->len), DMA_TO_DEVICE); + skb = (struct sk_buff *)ptr_from_txbd(cur_p); + if (skb) + dev_consume_skb_irq(skb); + cur_p->app1 = 0; + cur_p->app2 = 0; + cur_p->app3 = 0; + cur_p->app4 = 0; + + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += be32_to_cpu(cur_p->len); + + /* app0 must be visible last, as it is used to flag + * availability of the bd + */ + smp_mb(); + cur_p->app0 = 0; + + lp->tx_bd_ci++; + if (lp->tx_bd_ci >= lp->tx_bd_num) + lp->tx_bd_ci = 0; + + cur_p = &lp->tx_bd_v[lp->tx_bd_ci]; + stat = be32_to_cpu(cur_p->app0); + } + + /* Matches barrier in temac_start_xmit */ + smp_mb(); + + 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; + + /* Make sure to read next bd app0 after this one */ + rmb(); + + tail++; + if (tail >= lp->tx_bd_num) + tail = 0; + + cur_p = &lp->tx_bd_v[tail]; + num_frag--; + } while (num_frag >= 0); + + return 0; +} + +static netdev_tx_t +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 tail_p, skb_dma_addr; + int ii; + unsigned long num_frag; + skb_frag_t *frag; + + num_frag = skb_shinfo(skb)->nr_frags; + frag = &skb_shinfo(skb)->frags[0]; + cur_p = &lp->tx_bd_v[lp->tx_bd_tail]; + + if (temac_check_tx_bd_space(lp, num_frag + 1)) { + if (netif_queue_stopped(ndev)) + return NETDEV_TX_BUSY; + + netif_stop_queue(ndev); + + /* Matches barrier in temac_start_xmit_done */ + smp_mb(); + + /* Space might have just been freed - check again */ + if (temac_check_tx_bd_space(lp, num_frag + 1)) + return NETDEV_TX_BUSY; + + netif_wake_queue(ndev); + } + + 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 |= cpu_to_be32(0x000001); /* TX Checksum Enabled */ + cur_p->app1 = cpu_to_be32((csum_start_off << 16) + | csum_index_off); + cur_p->app2 = 0; /* initial checksum seed */ + } + + cur_p->app0 |= cpu_to_be32(STS_CTRL_APP0_SOP); + skb_dma_addr = dma_map_single(ndev->dev.parent, skb->data, + skb_headlen(skb), DMA_TO_DEVICE); + cur_p->len = cpu_to_be32(skb_headlen(skb)); + if (WARN_ON_ONCE(dma_mapping_error(ndev->dev.parent, skb_dma_addr))) { + dev_kfree_skb_any(skb); + ndev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + cur_p->phys = cpu_to_be32(skb_dma_addr); + + for (ii = 0; ii < num_frag; ii++) { + if (++lp->tx_bd_tail >= lp->tx_bd_num) + lp->tx_bd_tail = 0; + + cur_p = &lp->tx_bd_v[lp->tx_bd_tail]; + skb_dma_addr = dma_map_single(ndev->dev.parent, + skb_frag_address(frag), + skb_frag_size(frag), + DMA_TO_DEVICE); + if (dma_mapping_error(ndev->dev.parent, skb_dma_addr)) { + if (--lp->tx_bd_tail < 0) + lp->tx_bd_tail = lp->tx_bd_num - 1; + cur_p = &lp->tx_bd_v[lp->tx_bd_tail]; + while (--ii >= 0) { + --frag; + dma_unmap_single(ndev->dev.parent, + be32_to_cpu(cur_p->phys), + skb_frag_size(frag), + DMA_TO_DEVICE); + if (--lp->tx_bd_tail < 0) + lp->tx_bd_tail = lp->tx_bd_num - 1; + cur_p = &lp->tx_bd_v[lp->tx_bd_tail]; + } + dma_unmap_single(ndev->dev.parent, + be32_to_cpu(cur_p->phys), + skb_headlen(skb), DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + ndev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + cur_p->phys = cpu_to_be32(skb_dma_addr); + cur_p->len = cpu_to_be32(skb_frag_size(frag)); + cur_p->app0 = 0; + frag++; + } + cur_p->app0 |= cpu_to_be32(STS_CTRL_APP0_EOP); + + /* Mark last fragment with skb address, so it can be consumed + * in temac_start_xmit_done() + */ + ptr_to_txbd((void *)skb, cur_p); + + tail_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail; + lp->tx_bd_tail++; + if (lp->tx_bd_tail >= lp->tx_bd_num) + lp->tx_bd_tail = 0; + + skb_tx_timestamp(skb); + + /* Kick off the transfer */ + wmb(); + lp->dma_out(lp, TX_TAILDESC_PTR, tail_p); /* DMA start */ + + if (temac_check_tx_bd_space(lp, MAX_SKB_FRAGS + 1)) + netif_stop_queue(ndev); + + return NETDEV_TX_OK; +} + +static int ll_temac_recv_buffers_available(struct temac_local *lp) +{ + int available; + + if (!lp->rx_skb[lp->rx_bd_ci]) + return 0; + available = 1 + lp->rx_bd_tail - lp->rx_bd_ci; + if (available <= 0) + available += lp->rx_bd_num; + return available; +} + +static void ll_temac_recv(struct net_device *ndev) +{ + struct temac_local *lp = netdev_priv(ndev); + unsigned long flags; + int rx_bd; + bool update_tail = false; + + spin_lock_irqsave(&lp->rx_lock, flags); + + /* Process all received buffers, passing them on network + * stack. After this, the buffer descriptors will be in an + * un-allocated stage, where no skb is allocated for it, and + * they are therefore not available for TEMAC/DMA. + */ + do { + struct cdmac_bd *bd = &lp->rx_bd_v[lp->rx_bd_ci]; + struct sk_buff *skb = lp->rx_skb[lp->rx_bd_ci]; + unsigned int bdstat = be32_to_cpu(bd->app0); + int length; + + /* While this should not normally happen, we can end + * here when GFP_ATOMIC allocations fail, and we + * therefore have un-allocated buffers. + */ + if (!skb) + break; + + /* Loop over all completed buffer descriptors */ + if (!(bdstat & STS_CTRL_APP0_CMPLT)) + break; + + dma_unmap_single(ndev->dev.parent, be32_to_cpu(bd->phys), + XTE_MAX_JUMBO_FRAME_SIZE, DMA_FROM_DEVICE); + /* The buffer is not valid for DMA anymore */ + bd->phys = 0; + bd->len = 0; + + length = be32_to_cpu(bd->app4) & 0x3FFF; + 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)) { + /* Convert from device endianness (be32) to cpu + * endianness, and if necessary swap the bytes + * (back) for proper IP checksum byte order + * (be16). + */ + skb->csum = htons(be32_to_cpu(bd->app3) & 0xFFFF); + skb->ip_summed = CHECKSUM_COMPLETE; + } + + if (!skb_defer_rx_timestamp(skb)) + netif_rx(skb); + /* The skb buffer is now owned by network stack above */ + lp->rx_skb[lp->rx_bd_ci] = NULL; + + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += length; + + rx_bd = lp->rx_bd_ci; + if (++lp->rx_bd_ci >= lp->rx_bd_num) + lp->rx_bd_ci = 0; + } while (rx_bd != lp->rx_bd_tail); + + /* DMA operations will halt when the last buffer descriptor is + * processed (ie. the one pointed to by RX_TAILDESC_PTR). + * When that happens, no more interrupt events will be + * generated. No IRQ_COAL or IRQ_DLY, and not even an + * IRQ_ERR. To avoid stalling, we schedule a delayed work + * when there is a potential risk of that happening. The work + * will call this function, and thus re-schedule itself until + * enough buffers are available again. + */ + if (ll_temac_recv_buffers_available(lp) < lp->coalesce_count_rx) + schedule_delayed_work(&lp->restart_work, HZ / 1000); + + /* Allocate new buffers for those buffer descriptors that were + * passed to network stack. Note that GFP_ATOMIC allocations + * can fail (e.g. when a larger burst of GFP_ATOMIC + * allocations occurs), so while we try to allocate all + * buffers in the same interrupt where they were processed, we + * continue with what we could get in case of allocation + * failure. Allocation of remaining buffers will be retried + * in following calls. + */ + while (1) { + struct sk_buff *skb; + struct cdmac_bd *bd; + dma_addr_t skb_dma_addr; + + rx_bd = lp->rx_bd_tail + 1; + if (rx_bd >= lp->rx_bd_num) + rx_bd = 0; + bd = &lp->rx_bd_v[rx_bd]; + + if (bd->phys) + break; /* All skb's allocated */ + + skb = netdev_alloc_skb_ip_align(ndev, XTE_MAX_JUMBO_FRAME_SIZE); + if (!skb) { + dev_warn(&ndev->dev, "skb alloc failed\n"); + break; + } + + skb_dma_addr = dma_map_single(ndev->dev.parent, skb->data, + XTE_MAX_JUMBO_FRAME_SIZE, + DMA_FROM_DEVICE); + if (WARN_ON_ONCE(dma_mapping_error(ndev->dev.parent, + skb_dma_addr))) { + dev_kfree_skb_any(skb); + break; + } + + bd->phys = cpu_to_be32(skb_dma_addr); + bd->len = cpu_to_be32(XTE_MAX_JUMBO_FRAME_SIZE); + bd->app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND); + lp->rx_skb[rx_bd] = skb; + + lp->rx_bd_tail = rx_bd; + update_tail = true; + } + + /* Move tail pointer when buffers have been allocated */ + if (update_tail) { + lp->dma_out(lp, RX_TAILDESC_PTR, + lp->rx_bd_p + sizeof(*lp->rx_bd_v) * lp->rx_bd_tail); + } + + spin_unlock_irqrestore(&lp->rx_lock, flags); +} + +/* Function scheduled to ensure a restart in case of DMA halt + * condition caused by running out of buffer descriptors. + */ +static void ll_temac_restart_work_func(struct work_struct *work) +{ + struct temac_local *lp = container_of(work, struct temac_local, + restart_work.work); + struct net_device *ndev = lp->ndev; + + ll_temac_recv(ndev); +} + +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 & (IRQ_ERR | IRQ_DMAERR)) + dev_err_ratelimited(&ndev->dev, + "TX error 0x%x TX_CHNL_STS=0x%08x\n", + status, lp->dma_in(lp, TX_CHNL_STS)); + + 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); + if (status & (IRQ_ERR | IRQ_DMAERR)) + dev_err_ratelimited(&ndev->dev, + "RX error 0x%x RX_CHNL_STS=0x%08x\n", + status, lp->dma_in(lp, RX_CHNL_STS)); + + return IRQ_HANDLED; +} + +static int temac_open(struct net_device *ndev) +{ + struct temac_local *lp = netdev_priv(ndev); + struct phy_device *phydev = NULL; + int rc; + + dev_dbg(&ndev->dev, "temac_open()\n"); + + if (lp->phy_node) { + phydev = of_phy_connect(lp->ndev, lp->phy_node, + temac_adjust_link, 0, 0); + if (!phydev) { + dev_err(lp->dev, "of_phy_connect() failed\n"); + return -ENODEV; + } + phy_start(phydev); + } else if (strlen(lp->phy_name) > 0) { + phydev = phy_connect(lp->ndev, lp->phy_name, temac_adjust_link, + lp->phy_interface); + if (IS_ERR(phydev)) { + dev_err(lp->dev, "phy_connect() failed\n"); + return PTR_ERR(phydev); + } + phy_start(phydev); + } + + 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 (phydev) + phy_disconnect(phydev); + 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); + struct phy_device *phydev = ndev->phydev; + + dev_dbg(&ndev->dev, "temac_close()\n"); + + cancel_delayed_work_sync(&lp->restart_work); + + free_irq(lp->tx_irq, ndev); + free_irq(lp->rx_irq, ndev); + + if (phydev) + phy_disconnect(phydev); + + 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 const struct net_device_ops temac_netdev_ops = { + .ndo_open = temac_open, + .ndo_stop = temac_stop, + .ndo_start_xmit = temac_start_xmit, + .ndo_set_rx_mode = temac_set_multicast_list, + .ndo_set_mac_address = temac_set_mac_address, + .ndo_validate_addr = eth_validate_addr, + .ndo_eth_ioctl = phy_do_ioctl_running, +#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 void +ll_temac_ethtools_get_ringparam(struct net_device *ndev, + struct ethtool_ringparam *ering, + struct kernel_ethtool_ringparam *kernel_ering, + struct netlink_ext_ack *extack) +{ + struct temac_local *lp = netdev_priv(ndev); + + ering->rx_max_pending = RX_BD_NUM_MAX; + ering->rx_mini_max_pending = 0; + ering->rx_jumbo_max_pending = 0; + ering->tx_max_pending = TX_BD_NUM_MAX; + ering->rx_pending = lp->rx_bd_num; + ering->rx_mini_pending = 0; + ering->rx_jumbo_pending = 0; + ering->tx_pending = lp->tx_bd_num; +} + +static int +ll_temac_ethtools_set_ringparam(struct net_device *ndev, + struct ethtool_ringparam *ering, + struct kernel_ethtool_ringparam *kernel_ering, + struct netlink_ext_ack *extack) +{ + struct temac_local *lp = netdev_priv(ndev); + + if (ering->rx_pending > RX_BD_NUM_MAX || + ering->rx_mini_pending || + ering->rx_jumbo_pending || + ering->rx_pending > TX_BD_NUM_MAX) + return -EINVAL; + + if (netif_running(ndev)) + return -EBUSY; + + lp->rx_bd_num = ering->rx_pending; + lp->tx_bd_num = ering->tx_pending; + return 0; +} + +static int +ll_temac_ethtools_get_coalesce(struct net_device *ndev, + struct ethtool_coalesce *ec, + struct kernel_ethtool_coalesce *kernel_coal, + struct netlink_ext_ack *extack) +{ + struct temac_local *lp = netdev_priv(ndev); + + ec->rx_max_coalesced_frames = lp->coalesce_count_rx; + ec->tx_max_coalesced_frames = lp->coalesce_count_tx; + ec->rx_coalesce_usecs = (lp->coalesce_delay_rx * 512) / 100; + ec->tx_coalesce_usecs = (lp->coalesce_delay_tx * 512) / 100; + return 0; +} + +static int +ll_temac_ethtools_set_coalesce(struct net_device *ndev, + struct ethtool_coalesce *ec, + struct kernel_ethtool_coalesce *kernel_coal, + struct netlink_ext_ack *extack) +{ + struct temac_local *lp = netdev_priv(ndev); + + if (netif_running(ndev)) { + netdev_err(ndev, + "Please stop netif before applying configuration\n"); + return -EFAULT; + } + + if (ec->rx_max_coalesced_frames) + lp->coalesce_count_rx = ec->rx_max_coalesced_frames; + if (ec->tx_max_coalesced_frames) + lp->coalesce_count_tx = ec->tx_max_coalesced_frames; + /* With typical LocalLink clock speed of 200 MHz and + * C_PRESCALAR=1023, each delay count corresponds to 5.12 us. + */ + if (ec->rx_coalesce_usecs) + lp->coalesce_delay_rx = + min(255U, (ec->rx_coalesce_usecs * 100) / 512); + if (ec->tx_coalesce_usecs) + lp->coalesce_delay_tx = + min(255U, (ec->tx_coalesce_usecs * 100) / 512); + + return 0; +} + +static const struct ethtool_ops temac_ethtool_ops = { + .supported_coalesce_params = ETHTOOL_COALESCE_USECS | + ETHTOOL_COALESCE_MAX_FRAMES, + .nway_reset = phy_ethtool_nway_reset, + .get_link = ethtool_op_get_link, + .get_ts_info = ethtool_op_get_ts_info, + .get_link_ksettings = phy_ethtool_get_link_ksettings, + .set_link_ksettings = phy_ethtool_set_link_ksettings, + .get_ringparam = ll_temac_ethtools_get_ringparam, + .set_ringparam = ll_temac_ethtools_set_ringparam, + .get_coalesce = ll_temac_ethtools_get_coalesce, + .set_coalesce = ll_temac_ethtools_set_coalesce, +}; + +static int temac_probe(struct platform_device *pdev) +{ + struct ll_temac_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *temac_np = dev_of_node(&pdev->dev), *dma_np; + struct temac_local *lp; + struct net_device *ndev; + u8 addr[ETH_ALEN]; + __be32 *p; + bool little_endian; + int rc = 0; + + /* Init network device structure */ + ndev = devm_alloc_etherdev(&pdev->dev, sizeof(*lp)); + if (!ndev) + return -ENOMEM; + + platform_set_drvdata(pdev, ndev); + SET_NETDEV_DEV(ndev, &pdev->dev); + 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 = &pdev->dev; + lp->options = XTE_OPTION_DEFAULTS; + lp->rx_bd_num = RX_BD_NUM_DEFAULT; + lp->tx_bd_num = TX_BD_NUM_DEFAULT; + spin_lock_init(&lp->rx_lock); + INIT_DELAYED_WORK(&lp->restart_work, ll_temac_restart_work_func); + + /* Setup mutex for synchronization of indirect register access */ + if (pdata) { + if (!pdata->indirect_lock) { + dev_err(&pdev->dev, + "indirect_lock missing in platform_data\n"); + return -EINVAL; + } + lp->indirect_lock = pdata->indirect_lock; + } else { + lp->indirect_lock = devm_kmalloc(&pdev->dev, + sizeof(*lp->indirect_lock), + GFP_KERNEL); + if (!lp->indirect_lock) + return -ENOMEM; + spin_lock_init(lp->indirect_lock); + } + + /* map device registers */ + lp->regs = devm_platform_ioremap_resource_byname(pdev, 0); + if (IS_ERR(lp->regs)) { + dev_err(&pdev->dev, "could not map TEMAC registers\n"); + return -ENOMEM; + } + + /* Select register access functions with the specified + * endianness mode. Default for OF devices is big-endian. + */ + little_endian = false; + if (temac_np) { + if (of_get_property(temac_np, "little-endian", NULL)) + little_endian = true; + } else if (pdata) { + little_endian = pdata->reg_little_endian; + } + if (little_endian) { + lp->temac_ior = _temac_ior_le; + lp->temac_iow = _temac_iow_le; + } else { + lp->temac_ior = _temac_ior_be; + lp->temac_iow = _temac_iow_be; + } + + /* Setup checksum offload, but default to off if not specified */ + lp->temac_features = 0; + if (temac_np) { + p = (__be32 *)of_get_property(temac_np, "xlnx,txcsum", NULL); + if (p && be32_to_cpu(*p)) + lp->temac_features |= TEMAC_FEATURE_TX_CSUM; + p = (__be32 *)of_get_property(temac_np, "xlnx,rxcsum", NULL); + if (p && be32_to_cpu(*p)) + lp->temac_features |= TEMAC_FEATURE_RX_CSUM; + } else if (pdata) { + if (pdata->txcsum) + lp->temac_features |= TEMAC_FEATURE_TX_CSUM; + if (pdata->rxcsum) + lp->temac_features |= TEMAC_FEATURE_RX_CSUM; + } + if (lp->temac_features & TEMAC_FEATURE_TX_CSUM) + /* Can checksum TCP/UDP over IPv4. */ + ndev->features |= NETIF_F_IP_CSUM; + + /* Defaults for IRQ delay/coalescing setup. These are + * configuration values, so does not belong in device-tree. + */ + lp->coalesce_delay_tx = 0x10; + lp->coalesce_count_tx = 0x22; + lp->coalesce_delay_rx = 0xff; + lp->coalesce_count_rx = 0x07; + + /* Setup LocalLink DMA */ + if (temac_np) { + /* Find the DMA node, map the DMA registers, and + * decode the DMA IRQs. + */ + dma_np = of_parse_phandle(temac_np, "llink-connected", 0); + if (!dma_np) { + dev_err(&pdev->dev, "could not find DMA node\n"); + return -ENODEV; + } + + /* Setup the DMA register accesses, could be DCR or + * memory mapped. + */ + if (temac_dcr_setup(lp, pdev, dma_np)) { + /* no DCR in the device tree, try non-DCR */ + lp->sdma_regs = devm_of_iomap(&pdev->dev, dma_np, 0, + NULL); + if (IS_ERR(lp->sdma_regs)) { + dev_err(&pdev->dev, + "unable to map DMA registers\n"); + of_node_put(dma_np); + return PTR_ERR(lp->sdma_regs); + } + if (of_property_read_bool(dma_np, "little-endian")) { + lp->dma_in = temac_dma_in32_le; + lp->dma_out = temac_dma_out32_le; + } else { + lp->dma_in = temac_dma_in32_be; + lp->dma_out = temac_dma_out32_be; + } + dev_dbg(&pdev->dev, "MEM base: %p\n", lp->sdma_regs); + } + + /* Get DMA RX and TX interrupts */ + lp->rx_irq = irq_of_parse_and_map(dma_np, 0); + lp->tx_irq = irq_of_parse_and_map(dma_np, 1); + + /* Finished with the DMA node; drop the reference */ + of_node_put(dma_np); + } else if (pdata) { + /* 2nd memory resource specifies DMA registers */ + lp->sdma_regs = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(lp->sdma_regs)) { + dev_err(&pdev->dev, + "could not map DMA registers\n"); + return PTR_ERR(lp->sdma_regs); + } + if (pdata->dma_little_endian) { + lp->dma_in = temac_dma_in32_le; + lp->dma_out = temac_dma_out32_le; + } else { + lp->dma_in = temac_dma_in32_be; + lp->dma_out = temac_dma_out32_be; + } + + /* Get DMA RX and TX interrupts */ + lp->rx_irq = platform_get_irq(pdev, 0); + lp->tx_irq = platform_get_irq(pdev, 1); + + /* IRQ delay/coalescing setup */ + if (pdata->tx_irq_timeout || pdata->tx_irq_count) { + lp->coalesce_delay_tx = pdata->tx_irq_timeout; + lp->coalesce_count_tx = pdata->tx_irq_count; + } + if (pdata->rx_irq_timeout || pdata->rx_irq_count) { + lp->coalesce_delay_rx = pdata->rx_irq_timeout; + lp->coalesce_count_rx = pdata->rx_irq_count; + } + } + + /* Error handle returned DMA RX and TX interrupts */ + if (lp->rx_irq <= 0) { + rc = lp->rx_irq ?: -EINVAL; + return dev_err_probe(&pdev->dev, rc, + "could not get DMA RX irq\n"); + } + if (lp->tx_irq <= 0) { + rc = lp->tx_irq ?: -EINVAL; + return dev_err_probe(&pdev->dev, rc, + "could not get DMA TX irq\n"); + } + + if (temac_np) { + /* Retrieve the MAC address */ + rc = of_get_mac_address(temac_np, addr); + if (rc) { + dev_err(&pdev->dev, "could not find MAC address\n"); + return -ENODEV; + } + temac_init_mac_address(ndev, addr); + } else if (pdata) { + temac_init_mac_address(ndev, pdata->mac_addr); + } + + rc = temac_mdio_setup(lp, pdev); + if (rc) + dev_warn(&pdev->dev, "error registering MDIO bus\n"); + + if (temac_np) { + lp->phy_node = of_parse_phandle(temac_np, "phy-handle", 0); + if (lp->phy_node) + dev_dbg(lp->dev, "using PHY node %pOF\n", temac_np); + } else if (pdata) { + snprintf(lp->phy_name, sizeof(lp->phy_name), + PHY_ID_FMT, lp->mii_bus->id, pdata->phy_addr); + lp->phy_interface = pdata->phy_interface; + } + + /* 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_sysfs_create; + } + + 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_sysfs_create: + if (lp->phy_node) + of_node_put(lp->phy_node); + temac_mdio_teardown(lp); + return rc; +} + +static int temac_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct temac_local *lp = netdev_priv(ndev); + + unregister_netdev(ndev); + sysfs_remove_group(&lp->dev->kobj, &temac_attr_group); + if (lp->phy_node) + of_node_put(lp->phy_node); + temac_mdio_teardown(lp); + return 0; +} + +static const 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_driver = { + .probe = temac_probe, + .remove = temac_remove, + .driver = { + .name = "xilinx_temac", + .of_match_table = temac_of_match, + }, +}; + +module_platform_driver(temac_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 000000000..2371c072b --- /dev/null +++ b/drivers/net/ethernet/xilinx/ll_temac_mdio.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 <linux/platform_data/xilinx-ll-temac.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; + unsigned long flags; + + /* Write the PHY address to the MIIM Access Initiator register. + * When the transfer completes, the PHY register value will appear + * in the LSW0 register + */ + spin_lock_irqsave(lp->indirect_lock, flags); + temac_iow(lp, XTE_LSW0_OFFSET, (phy_id << 5) | reg); + rc = temac_indirect_in32_locked(lp, XTE_MIIMAI_OFFSET); + spin_unlock_irqrestore(lp->indirect_lock, flags); + + 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; + unsigned long flags; + + 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 + */ + spin_lock_irqsave(lp->indirect_lock, flags); + temac_indirect_out32_locked(lp, XTE_MGTDR_OFFSET, val); + temac_indirect_out32_locked(lp, XTE_MIIMAI_OFFSET, (phy_id << 5) | reg); + spin_unlock_irqrestore(lp->indirect_lock, flags); + + return 0; +} + +int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev) +{ + struct ll_temac_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *np = dev_of_node(&pdev->dev); + struct mii_bus *bus; + u32 bus_hz; + int clk_div; + int rc; + struct resource res; + + /* Get MDIO bus frequency (if specified) */ + bus_hz = 0; + if (np) + of_property_read_u32(np, "clock-frequency", &bus_hz); + else if (pdata) + bus_hz = pdata->mdio_clk_freq; + + /* Calculate a reasonable divisor for the clock rate */ + clk_div = 0x3f; /* worst-case default setting */ + if (bus_hz != 0) { + 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 + */ + temac_indirect_out32(lp, XTE_MC_OFFSET, 1 << 6 | clk_div); + + bus = devm_mdiobus_alloc(&pdev->dev); + if (!bus) + return -ENOMEM; + + if (np) { + of_address_to_resource(np, 0, &res); + snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx", + (unsigned long long)res.start); + } else if (pdata) { + snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx", + pdata->mdio_bus_id); + } + + bus->priv = lp; + bus->name = "Xilinx TEMAC MDIO"; + bus->read = temac_mdio_read; + bus->write = temac_mdio_write; + bus->parent = lp->dev; + + lp->mii_bus = bus; + + rc = of_mdiobus_register(bus, np); + if (rc) + return rc; + + dev_dbg(lp->dev, "MDIO bus registered; MC:%x\n", + temac_indirect_in32(lp, XTE_MC_OFFSET)); + return 0; +} + +void temac_mdio_teardown(struct temac_local *lp) +{ + mdiobus_unregister(lp->mii_bus); +} diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h new file mode 100644 index 000000000..6370c447a --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -0,0 +1,619 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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> +#include <linux/if_vlan.h> +#include <linux/phylink.h> + +/* Packet size info */ +#define XAE_HDR_SIZE 14 /* Size of Ethernet header */ +#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 + VLAN_ETH_HLEN + 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_SR_HALT_MASK 0x00000001 /* Indicates DMA channel halted */ + +#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 delay timer values for SGDMA mode */ +#define XAXIDMA_DFT_TX_THRESHOLD 24 +#define XAXIDMA_DFT_TX_USEC 50 +#define XAXIDMA_DFT_RX_THRESHOLD 1 +#define XAXIDMA_DFT_RX_USEC 50 + +#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_ID_OFFSET 0x000004F8 /* Identification register */ +#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_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 */ +/* Reject receive multicast destination address */ +#define XAE_RAF_MCSTREJ_MASK 0x00000002 +/* Reject receive broadcast destination address */ +#define XAE_RAF_BCSTREJ_MASK 0x00000004 +#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 */ +/* Extended Multicast Filtering mode */ +#define XAE_RAF_EMULTIFLTRENBL_MASK 0x00001000 +#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 */ +/* Transmit inter-frame gap adjustment value */ +#define XAE_IFGP0_IFGP_MASK 0x0000007F + +/* Bit Masks for Axi Ethernet IS, IE and IP registers, Same masks apply + * for all 3 registers. + */ +/* Hard register access complete */ +#define XAE_INT_HARDACSCMPLT_MASK 0x00000001 +/* Auto negotiation complete */ +#define XAE_INT_AUTONEG_MASK 0x00000002 +#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 */ + +/* INT bits that indicate receive errors */ +#define XAE_INT_RECV_ERROR_MASK \ + (XAE_INT_RXRJECT_MASK | XAE_INT_RXFIFOOVR_MASK) + +/* 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 */ +/* In-Band FCS enable (FCS not stripped) */ +#define XAE_RCW1_FCS_MASK 0x20000000 +#define XAE_RCW1_RX_MASK 0x10000000 /* Receiver enable */ +#define XAE_RCW1_VLAN_MASK 0x08000000 /* VLAN frame enable */ +/* Length/type field valid check disable */ +#define XAE_RCW1_LT_DIS_MASK 0x02000000 +/* Control frame Length check disable */ +#define XAE_RCW1_CL_DIS_MASK 0x01000000 +/* Pause frame source address bits [47:32]. Bits [31:0] are + * stored in register RCW0 + */ +#define XAE_RCW1_PAUSEADDR_MASK 0x0000FFFF + +/* Bit masks for Axi Ethernet TC register */ +#define XAE_TC_RST_MASK 0x80000000 /* Reset */ +#define XAE_TC_JUM_MASK 0x40000000 /* Jumbo frame enable */ +/* In-Band FCS enable (FCS not generated) */ +#define XAE_TC_FCS_MASK 0x20000000 +#define XAE_TC_TX_MASK 0x10000000 /* Transmitter enable */ +#define XAE_TC_VLAN_MASK 0x08000000 /* VLAN frame enable */ +/* Inter-frame gap adjustment enable */ +#define XAE_TC_IFG_MASK 0x02000000 + +/* 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 */ +/* Station address bits [47:32]; Station address + * bits [31:0] are stored in register UAW0 + */ +#define XAE_UAW1_UNICASTADDR_MASK 0x0000FFFF + +/* 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 + + /* Total number of entries in the hardware multicast table. */ +#define XAE_MULTICAST_CAM_TABLE_NUM 4 + +/* 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_FEATURE_DMA_64BIT (1 << 4) + +#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 + +/* Xilinx PCS/PMA PHY register for switching 1000BaseX or SGMII */ +#define XLNX_MII_STD_SELECT_REG 0x11 +#define XLNX_MII_STD_SELECT_SGMII BIT(0) + +/** + * struct axidma_bd - Axi Dma buffer descriptor layout + * @next: MM2S/S2MM Next Descriptor Pointer + * @next_msb: MM2S/S2MM Next Descriptor Pointer (high 32 bits) + * @phys: MM2S/S2MM Buffer Address + * @phys_msb: MM2S/S2MM Buffer Address (high 32 bits) + * @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. + */ +struct axidma_bd { + u32 next; /* Physical address of next buffer descriptor */ + u32 next_msb; /* high 32 bits for IP >= v7.1, reserved on older IP */ + u32 phys; + u32 phys_msb; /* for IP >= v7.1, reserved for older IP */ + 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; /* Last field used by HW */ + struct sk_buff *skb; +} __aligned(XAXIDMA_BD_MINIMUM_ALIGNMENT); + +#define XAE_NUM_MISC_CLOCKS 3 + +/** + * 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_node: Pointer to device node structure + * @phylink: Pointer to phylink instance + * @phylink_config: phylink configuration settings + * @pcs_phy: Reference to PCS/PMA PHY if used + * @pcs: phylink pcs structure for PCS PHY + * @switch_x_sgmii: Whether switchable 1000BaseX/SGMII mode is enabled in the core + * @axi_clk: AXI4-Lite bus clock + * @misc_clks: Misc ethernet clocks (AXI4-Stream, Ref, MGT clocks) + * @mii_bus: Pointer to MII bus structure + * @mii_clk_div: MII bus clock divider value + * @regs_start: Resource start for axienet device addresses + * @regs: Base address for the axienet_local device address space + * @dma_regs: Base address for the axidma device address space + * @napi_rx: NAPI RX control structure + * @rx_dma_cr: Nominal content of RX DMA control register + * @rx_bd_v: Virtual address of the RX buffer descriptor ring + * @rx_bd_p: Physical address(start address) of the RX buffer descr. ring + * @rx_bd_num: Size of RX buffer descriptor ring + * @rx_bd_ci: Stores the index of the Rx buffer descriptor in the ring being + * accessed currently. + * @rx_packets: RX packet count for statistics + * @rx_bytes: RX byte count for statistics + * @rx_stat_sync: Synchronization object for RX stats + * @napi_tx: NAPI TX control structure + * @tx_dma_cr: Nominal content of TX DMA control register + * @tx_bd_v: Virtual address of the TX buffer descriptor ring + * @tx_bd_p: Physical address(start address) of the TX buffer descr. ring + * @tx_bd_num: Size of TX buffer descriptor ring + * @tx_bd_ci: Stores the next Tx buffer descriptor in the ring that may be + * complete. Only updated at runtime by TX NAPI poll. + * @tx_bd_tail: Stores the index of the next Tx buffer descriptor in the ring + * to be populated. + * @tx_packets: TX packet count for statistics + * @tx_bytes: TX byte count for statistics + * @tx_stat_sync: Synchronization object for TX stats + * @dma_err_task: Work structure to process Axi DMA errors + * @tx_irq: Axidma TX IRQ number + * @rx_irq: Axidma RX IRQ number + * @eth_irq: Ethernet core IRQ number + * @phy_mode: Phy type to identify between MII/GMII/RGMII/SGMII/1000 Base-X + * @options: AxiEthernet option word + * @features: Stores the extended features supported by the axienet hw + * @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) + * @rxmem: Stores rx memory size for jumbo frame handling. + * @csum_offload_on_tx_path: Stores the checksum selection on TX side. + * @csum_offload_on_rx_path: Stores the checksum selection on RX side. + * @coalesce_count_rx: Store the irq coalesce on RX side. + * @coalesce_usec_rx: IRQ coalesce delay for RX + * @coalesce_count_tx: Store the irq coalesce on TX side. + * @coalesce_usec_tx: IRQ coalesce delay for TX + */ +struct axienet_local { + struct net_device *ndev; + struct device *dev; + + struct phylink *phylink; + struct phylink_config phylink_config; + + struct mdio_device *pcs_phy; + struct phylink_pcs pcs; + + bool switch_x_sgmii; + + struct clk *axi_clk; + struct clk_bulk_data misc_clks[XAE_NUM_MISC_CLOCKS]; + + struct mii_bus *mii_bus; + u8 mii_clk_div; + + resource_size_t regs_start; + void __iomem *regs; + void __iomem *dma_regs; + + struct napi_struct napi_rx; + u32 rx_dma_cr; + struct axidma_bd *rx_bd_v; + dma_addr_t rx_bd_p; + u32 rx_bd_num; + u32 rx_bd_ci; + u64_stats_t rx_packets; + u64_stats_t rx_bytes; + struct u64_stats_sync rx_stat_sync; + + struct napi_struct napi_tx; + u32 tx_dma_cr; + struct axidma_bd *tx_bd_v; + dma_addr_t tx_bd_p; + u32 tx_bd_num; + u32 tx_bd_ci; + u32 tx_bd_tail; + u64_stats_t tx_packets; + u64_stats_t tx_bytes; + struct u64_stats_sync tx_stat_sync; + + struct work_struct dma_err_task; + + int tx_irq; + int rx_irq; + int eth_irq; + phy_interface_t phy_mode; + + u32 options; + u32 features; + + u32 max_frm_size; + u32 rxmem; + + int csum_offload_on_tx_path; + int csum_offload_on_rx_path; + + u32 coalesce_count_rx; + u32 coalesce_usec_rx; + u32 coalesce_count_tx; + u32 coalesce_usec_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 + * + * Return: 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 ioread32(lp->regs + offset); +} + +static inline u32 axinet_ior_read_mcr(struct axienet_local *lp) +{ + return axienet_ior(lp, XAE_MDIO_MCR_OFFSET); +} + +static inline void axienet_lock_mii(struct axienet_local *lp) +{ + if (lp->mii_bus) + mutex_lock(&lp->mii_bus->mdio_lock); +} + +static inline void axienet_unlock_mii(struct axienet_local *lp) +{ + if (lp->mii_bus) + mutex_unlock(&lp->mii_bus->mdio_lock); +} + +/** + * 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) +{ + iowrite32(value, lp->regs + offset); +} + +/** + * 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) +{ + iowrite32(value, lp->dma_regs + reg); +} + +#if defined(CONFIG_64BIT) && defined(iowrite64) +/** + * axienet_dma_out64 - 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_out64(struct axienet_local *lp, + off_t reg, u64 value) +{ + iowrite64(value, lp->dma_regs + reg); +} + +static inline void axienet_dma_out_addr(struct axienet_local *lp, off_t reg, + dma_addr_t addr) +{ + if (lp->features & XAE_FEATURE_DMA_64BIT) + axienet_dma_out64(lp, reg, addr); + else + axienet_dma_out32(lp, reg, lower_32_bits(addr)); +} + +#else /* CONFIG_64BIT */ + +static inline void axienet_dma_out_addr(struct axienet_local *lp, off_t reg, + dma_addr_t addr) +{ + axienet_dma_out32(lp, reg, lower_32_bits(addr)); +} + +#endif /* CONFIG_64BIT */ + +/* Function prototypes visible in xilinx_axienet_mdio.c for other files */ +int axienet_mdio_enable(struct axienet_local *lp); +void axienet_mdio_disable(struct axienet_local *lp); +int axienet_mdio_setup(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 000000000..5ea9dc251 --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -0,0 +1,2234 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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) 2019 - 2022 Calian Advanced Technologies + * 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/clk.h> +#include <linux/delay.h> +#include <linux/etherdevice.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/of_mdio.h> +#include <linux/of_net.h> +#include <linux/of_platform.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <linux/skbuff.h> +#include <linux/math64.h> +#include <linux/phy.h> +#include <linux/mii.h> +#include <linux/ethtool.h> + +#include "xilinx_axienet.h" + +/* Descriptors defines for Tx and Rx DMA */ +#define TX_BD_NUM_DEFAULT 128 +#define RX_BD_NUM_DEFAULT 1024 +#define TX_BD_NUM_MIN (MAX_SKB_FRAGS + 1) +#define TX_BD_NUM_MAX 4096 +#define RX_BD_NUM_MAX 4096 + +/* 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 40 + +/* Match table for of_platform binding */ +static const 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 + * + * Return: 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 ioread32(lp->dma_regs + reg); +} + +static void desc_set_phys_addr(struct axienet_local *lp, dma_addr_t addr, + struct axidma_bd *desc) +{ + desc->phys = lower_32_bits(addr); + if (lp->features & XAE_FEATURE_DMA_64BIT) + desc->phys_msb = upper_32_bits(addr); +} + +static dma_addr_t desc_get_phys_addr(struct axienet_local *lp, + struct axidma_bd *desc) +{ + dma_addr_t ret = desc->phys; + + if (lp->features & XAE_FEATURE_DMA_64BIT) + ret |= ((dma_addr_t)desc->phys_msb << 16) << 16; + + return ret; +} + +/** + * 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); + + /* If we end up here, tx_bd_v must have been DMA allocated. */ + dma_free_coherent(lp->dev, + sizeof(*lp->tx_bd_v) * lp->tx_bd_num, + lp->tx_bd_v, + lp->tx_bd_p); + + if (!lp->rx_bd_v) + return; + + for (i = 0; i < lp->rx_bd_num; i++) { + dma_addr_t phys; + + /* A NULL skb means this descriptor has not been initialised + * at all. + */ + if (!lp->rx_bd_v[i].skb) + break; + + dev_kfree_skb(lp->rx_bd_v[i].skb); + + /* For each descriptor, we programmed cntrl with the (non-zero) + * descriptor size, after it had been successfully allocated. + * So a non-zero value in there means we need to unmap it. + */ + if (lp->rx_bd_v[i].cntrl) { + phys = desc_get_phys_addr(lp, &lp->rx_bd_v[i]); + dma_unmap_single(lp->dev, phys, + lp->max_frm_size, DMA_FROM_DEVICE); + } + } + + dma_free_coherent(lp->dev, + sizeof(*lp->rx_bd_v) * lp->rx_bd_num, + lp->rx_bd_v, + lp->rx_bd_p); +} + +/** + * axienet_usec_to_timer - Calculate IRQ delay timer value + * @lp: Pointer to the axienet_local structure + * @coalesce_usec: Microseconds to convert into timer value + */ +static u32 axienet_usec_to_timer(struct axienet_local *lp, u32 coalesce_usec) +{ + u32 result; + u64 clk_rate = 125000000; /* arbitrary guess if no clock rate set */ + + if (lp->axi_clk) + clk_rate = clk_get_rate(lp->axi_clk); + + /* 1 Timeout Interval = 125 * (clock period of SG clock) */ + result = DIV64_U64_ROUND_CLOSEST((u64)coalesce_usec * clk_rate, + (u64)125000000); + if (result > 255) + result = 255; + + return result; +} + +/** + * axienet_dma_start - Set up DMA registers and start DMA operation + * @lp: Pointer to the axienet_local structure + */ +static void axienet_dma_start(struct axienet_local *lp) +{ + /* Start updating the Rx channel control register */ + lp->rx_dma_cr = (lp->coalesce_count_rx << XAXIDMA_COALESCE_SHIFT) | + XAXIDMA_IRQ_IOC_MASK | XAXIDMA_IRQ_ERROR_MASK; + /* Only set interrupt delay timer if not generating an interrupt on + * the first RX packet. Otherwise leave at 0 to disable delay interrupt. + */ + if (lp->coalesce_count_rx > 1) + lp->rx_dma_cr |= (axienet_usec_to_timer(lp, lp->coalesce_usec_rx) + << XAXIDMA_DELAY_SHIFT) | + XAXIDMA_IRQ_DELAY_MASK; + axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, lp->rx_dma_cr); + + /* Start updating the Tx channel control register */ + lp->tx_dma_cr = (lp->coalesce_count_tx << XAXIDMA_COALESCE_SHIFT) | + XAXIDMA_IRQ_IOC_MASK | XAXIDMA_IRQ_ERROR_MASK; + /* Only set interrupt delay timer if not generating an interrupt on + * the first TX packet. Otherwise leave at 0 to disable delay interrupt. + */ + if (lp->coalesce_count_tx > 1) + lp->tx_dma_cr |= (axienet_usec_to_timer(lp, lp->coalesce_usec_tx) + << XAXIDMA_DELAY_SHIFT) | + XAXIDMA_IRQ_DELAY_MASK; + axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, lp->tx_dma_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_out_addr(lp, XAXIDMA_RX_CDESC_OFFSET, lp->rx_bd_p); + lp->rx_dma_cr |= XAXIDMA_CR_RUNSTOP_MASK; + axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, lp->rx_dma_cr); + axienet_dma_out_addr(lp, XAXIDMA_RX_TDESC_OFFSET, lp->rx_bd_p + + (sizeof(*lp->rx_bd_v) * (lp->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_out_addr(lp, XAXIDMA_TX_CDESC_OFFSET, lp->tx_bd_p); + lp->tx_dma_cr |= XAXIDMA_CR_RUNSTOP_MASK; + axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, lp->tx_dma_cr); +} + +/** + * axienet_dma_bd_init - Setup buffer descriptor rings for Axi DMA + * @ndev: Pointer to the net_device structure + * + * Return: 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) +{ + 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_alloc_coherent(lp->dev, + sizeof(*lp->tx_bd_v) * lp->tx_bd_num, + &lp->tx_bd_p, GFP_KERNEL); + if (!lp->tx_bd_v) + return -ENOMEM; + + lp->rx_bd_v = dma_alloc_coherent(lp->dev, + sizeof(*lp->rx_bd_v) * lp->rx_bd_num, + &lp->rx_bd_p, GFP_KERNEL); + if (!lp->rx_bd_v) + goto out; + + for (i = 0; i < lp->tx_bd_num; i++) { + dma_addr_t addr = lp->tx_bd_p + + sizeof(*lp->tx_bd_v) * + ((i + 1) % lp->tx_bd_num); + + lp->tx_bd_v[i].next = lower_32_bits(addr); + if (lp->features & XAE_FEATURE_DMA_64BIT) + lp->tx_bd_v[i].next_msb = upper_32_bits(addr); + } + + for (i = 0; i < lp->rx_bd_num; i++) { + dma_addr_t addr; + + addr = lp->rx_bd_p + sizeof(*lp->rx_bd_v) * + ((i + 1) % lp->rx_bd_num); + lp->rx_bd_v[i].next = lower_32_bits(addr); + if (lp->features & XAE_FEATURE_DMA_64BIT) + lp->rx_bd_v[i].next_msb = upper_32_bits(addr); + + skb = netdev_alloc_skb_ip_align(ndev, lp->max_frm_size); + if (!skb) + goto out; + + lp->rx_bd_v[i].skb = skb; + addr = dma_map_single(lp->dev, skb->data, + lp->max_frm_size, DMA_FROM_DEVICE); + if (dma_mapping_error(lp->dev, addr)) { + netdev_err(ndev, "DMA mapping error\n"); + goto out; + } + desc_set_phys_addr(lp, addr, &lp->rx_bd_v[i]); + + lp->rx_bd_v[i].cntrl = lp->max_frm_size; + } + + axienet_dma_start(lp); + + 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, + const void *address) +{ + struct axienet_local *lp = netdev_priv(ndev); + + if (address) + eth_hw_addr_set(ndev, address); + if (!is_valid_ether_addr(ndev->dev_addr)) + eth_hw_addr_random(ndev); + + /* 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 + * + * Return: 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 int __axienet_device_reset(struct axienet_local *lp) +{ + u32 value; + int ret; + + /* 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. + * Note that even though both TX and RX have their own reset register, + * they both reset the entire DMA core, so only one needs to be used. + */ + axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, XAXIDMA_CR_RESET_MASK); + ret = read_poll_timeout(axienet_dma_in32, value, + !(value & XAXIDMA_CR_RESET_MASK), + DELAY_OF_ONE_MILLISEC, 50000, false, lp, + XAXIDMA_TX_CR_OFFSET); + if (ret) { + dev_err(lp->dev, "%s: DMA reset timeout!\n", __func__); + return ret; + } + + /* Wait for PhyRstCmplt bit to be set, indicating the PHY reset has finished */ + ret = read_poll_timeout(axienet_ior, value, + value & XAE_INT_PHYRSTCMPLT_MASK, + DELAY_OF_ONE_MILLISEC, 50000, false, lp, + XAE_IS_OFFSET); + if (ret) { + dev_err(lp->dev, "%s: timeout waiting for PhyRstCmplt\n", __func__); + return ret; + } + + return 0; +} + +/** + * axienet_dma_stop - Stop DMA operation + * @lp: Pointer to the axienet_local structure + */ +static void axienet_dma_stop(struct axienet_local *lp) +{ + int count; + u32 cr, sr; + + cr = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); + cr &= ~(XAXIDMA_CR_RUNSTOP_MASK | XAXIDMA_IRQ_ALL_MASK); + axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, cr); + synchronize_irq(lp->rx_irq); + + cr = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); + cr &= ~(XAXIDMA_CR_RUNSTOP_MASK | XAXIDMA_IRQ_ALL_MASK); + axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, cr); + synchronize_irq(lp->tx_irq); + + /* Give DMAs a chance to halt gracefully */ + sr = axienet_dma_in32(lp, XAXIDMA_RX_SR_OFFSET); + for (count = 0; !(sr & XAXIDMA_SR_HALT_MASK) && count < 5; ++count) { + msleep(20); + sr = axienet_dma_in32(lp, XAXIDMA_RX_SR_OFFSET); + } + + sr = axienet_dma_in32(lp, XAXIDMA_TX_SR_OFFSET); + for (count = 0; !(sr & XAXIDMA_SR_HALT_MASK) && count < 5; ++count) { + msleep(20); + sr = axienet_dma_in32(lp, XAXIDMA_TX_SR_OFFSET); + } + + /* Do a reset to ensure DMA is really stopped */ + axienet_lock_mii(lp); + __axienet_device_reset(lp); + axienet_unlock_mii(lp); +} + +/** + * 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 + * are connected to Axi Ethernet reset lines, this in turn resets the Axi + * Ethernet core. No separate hardware reset is done for the Axi Ethernet + * core. + * Returns 0 on success or a negative error number otherwise. + */ +static int axienet_device_reset(struct net_device *ndev) +{ + u32 axienet_status; + struct axienet_local *lp = netdev_priv(ndev); + int ret; + + ret = __axienet_device_reset(lp); + if (ret) + return ret; + + lp->max_frm_size = XAE_MAX_VLAN_FRAME_SIZE; + lp->options |= XAE_OPTION_VLAN; + lp->options &= (~XAE_OPTION_JUMBO); + + if ((ndev->mtu > XAE_MTU) && + (ndev->mtu <= XAE_JUMBO_MTU)) { + lp->max_frm_size = ndev->mtu + VLAN_ETH_HLEN + + XAE_TRL_SIZE; + + if (lp->max_frm_size <= lp->rxmem) + lp->options |= XAE_OPTION_JUMBO; + } + + ret = axienet_dma_bd_init(ndev); + if (ret) { + netdev_err(ndev, "%s: descriptor allocation failed\n", + __func__); + return ret; + } + + 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_IE_OFFSET, lp->eth_irq > 0 ? + XAE_INT_RECV_ERROR_MASK : 0); + + 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); + + netif_trans_update(ndev); + + return 0; +} + +/** + * axienet_free_tx_chain - Clean up a series of linked TX descriptors. + * @lp: Pointer to the axienet_local structure + * @first_bd: Index of first descriptor to clean up + * @nr_bds: Max number of descriptors to clean up + * @force: Whether to clean descriptors even if not complete + * @sizep: Pointer to a u32 filled with the total sum of all bytes + * in all cleaned-up descriptors. Ignored if NULL. + * @budget: NAPI budget (use 0 when not called from NAPI poll) + * + * Would either be called after a successful transmit operation, or after + * there was an error when setting up the chain. + * Returns the number of descriptors handled. + */ +static int axienet_free_tx_chain(struct axienet_local *lp, u32 first_bd, + int nr_bds, bool force, u32 *sizep, int budget) +{ + struct axidma_bd *cur_p; + unsigned int status; + dma_addr_t phys; + int i; + + for (i = 0; i < nr_bds; i++) { + cur_p = &lp->tx_bd_v[(first_bd + i) % lp->tx_bd_num]; + status = cur_p->status; + + /* If force is not specified, clean up only descriptors + * that have been completed by the MAC. + */ + if (!force && !(status & XAXIDMA_BD_STS_COMPLETE_MASK)) + break; + + /* Ensure we see complete descriptor update */ + dma_rmb(); + phys = desc_get_phys_addr(lp, cur_p); + dma_unmap_single(lp->dev, phys, + (cur_p->cntrl & XAXIDMA_BD_CTRL_LENGTH_MASK), + DMA_TO_DEVICE); + + if (cur_p->skb && (status & XAXIDMA_BD_STS_COMPLETE_MASK)) + napi_consume_skb(cur_p->skb, budget); + + cur_p->app0 = 0; + cur_p->app1 = 0; + cur_p->app2 = 0; + cur_p->app4 = 0; + cur_p->skb = NULL; + /* ensure our transmit path and device don't prematurely see status cleared */ + wmb(); + cur_p->cntrl = 0; + cur_p->status = 0; + + if (sizep) + *sizep += status & XAXIDMA_BD_STS_ACTUAL_LEN_MASK; + } + + return i; +} + +/** + * 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 + * + * Return: 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. + */ +static inline int axienet_check_tx_bd_space(struct axienet_local *lp, + int num_frag) +{ + struct axidma_bd *cur_p; + + /* Ensure we see all descriptor updates from device or TX polling */ + rmb(); + cur_p = &lp->tx_bd_v[(READ_ONCE(lp->tx_bd_tail) + num_frag) % + lp->tx_bd_num]; + if (cur_p->cntrl) + return NETDEV_TX_BUSY; + return 0; +} + +/** + * axienet_tx_poll - Invoked once a transmit is completed by the + * Axi DMA Tx channel. + * @napi: Pointer to NAPI structure. + * @budget: Max number of TX packets to process. + * + * Return: Number of TX packets processed. + * + * This function is invoked from the NAPI processing 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 int axienet_tx_poll(struct napi_struct *napi, int budget) +{ + struct axienet_local *lp = container_of(napi, struct axienet_local, napi_tx); + struct net_device *ndev = lp->ndev; + u32 size = 0; + int packets; + + packets = axienet_free_tx_chain(lp, lp->tx_bd_ci, budget, false, &size, budget); + + if (packets) { + lp->tx_bd_ci += packets; + if (lp->tx_bd_ci >= lp->tx_bd_num) + lp->tx_bd_ci %= lp->tx_bd_num; + + u64_stats_update_begin(&lp->tx_stat_sync); + u64_stats_add(&lp->tx_packets, packets); + u64_stats_add(&lp->tx_bytes, size); + u64_stats_update_end(&lp->tx_stat_sync); + + /* Matches barrier in axienet_start_xmit */ + smp_mb(); + + if (!axienet_check_tx_bd_space(lp, MAX_SKB_FRAGS + 1)) + netif_wake_queue(ndev); + } + + if (packets < budget && napi_complete_done(napi, packets)) { + /* Re-enable TX completion interrupts. This should + * cause an immediate interrupt if any TX packets are + * already pending. + */ + axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, lp->tx_dma_cr); + } + return packets; +} + +/** + * axienet_start_xmit - Starts the transmission. + * @skb: sk_buff pointer that contains data to be Txed. + * @ndev: Pointer to net_device structure. + * + * Return: 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 netdev_tx_t +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, phys; + u32 orig_tail_ptr, new_tail_ptr; + struct axienet_local *lp = netdev_priv(ndev); + struct axidma_bd *cur_p; + + orig_tail_ptr = lp->tx_bd_tail; + new_tail_ptr = orig_tail_ptr; + + num_frag = skb_shinfo(skb)->nr_frags; + cur_p = &lp->tx_bd_v[orig_tail_ptr]; + + if (axienet_check_tx_bd_space(lp, num_frag + 1)) { + /* Should not happen as last start_xmit call should have + * checked for sufficient space and queue should only be + * woken when sufficient space is available. + */ + netif_stop_queue(ndev); + if (net_ratelimit()) + netdev_warn(ndev, "TX ring unexpectedly full\n"); + 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_TX_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 */ + } + + phys = dma_map_single(lp->dev, skb->data, + skb_headlen(skb), DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(lp->dev, phys))) { + if (net_ratelimit()) + netdev_err(ndev, "TX DMA mapping error\n"); + ndev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + desc_set_phys_addr(lp, phys, cur_p); + cur_p->cntrl = skb_headlen(skb) | XAXIDMA_BD_CTRL_TXSOF_MASK; + + for (ii = 0; ii < num_frag; ii++) { + if (++new_tail_ptr >= lp->tx_bd_num) + new_tail_ptr = 0; + cur_p = &lp->tx_bd_v[new_tail_ptr]; + frag = &skb_shinfo(skb)->frags[ii]; + phys = dma_map_single(lp->dev, + skb_frag_address(frag), + skb_frag_size(frag), + DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(lp->dev, phys))) { + if (net_ratelimit()) + netdev_err(ndev, "TX DMA mapping error\n"); + ndev->stats.tx_dropped++; + axienet_free_tx_chain(lp, orig_tail_ptr, ii + 1, + true, NULL, 0); + return NETDEV_TX_OK; + } + desc_set_phys_addr(lp, phys, cur_p); + cur_p->cntrl = skb_frag_size(frag); + } + + cur_p->cntrl |= XAXIDMA_BD_CTRL_TXEOF_MASK; + cur_p->skb = skb; + + tail_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * new_tail_ptr; + if (++new_tail_ptr >= lp->tx_bd_num) + new_tail_ptr = 0; + WRITE_ONCE(lp->tx_bd_tail, new_tail_ptr); + + /* Start the transfer */ + axienet_dma_out_addr(lp, XAXIDMA_TX_TDESC_OFFSET, tail_p); + + /* Stop queue if next transmit may not have space */ + if (axienet_check_tx_bd_space(lp, MAX_SKB_FRAGS + 1)) { + netif_stop_queue(ndev); + + /* Matches barrier in axienet_tx_poll */ + smp_mb(); + + /* Space might have just been freed - check again */ + if (!axienet_check_tx_bd_space(lp, MAX_SKB_FRAGS + 1)) + netif_wake_queue(ndev); + } + + return NETDEV_TX_OK; +} + +/** + * axienet_rx_poll - Triggered by RX ISR to complete the BD processing. + * @napi: Pointer to NAPI structure. + * @budget: Max number of RX packets to process. + * + * Return: Number of RX packets processed. + */ +static int axienet_rx_poll(struct napi_struct *napi, int budget) +{ + u32 length; + u32 csumstatus; + u32 size = 0; + int packets = 0; + dma_addr_t tail_p = 0; + struct axidma_bd *cur_p; + struct sk_buff *skb, *new_skb; + struct axienet_local *lp = container_of(napi, struct axienet_local, napi_rx); + + cur_p = &lp->rx_bd_v[lp->rx_bd_ci]; + + while (packets < budget && (cur_p->status & XAXIDMA_BD_STS_COMPLETE_MASK)) { + dma_addr_t phys; + + /* Ensure we see complete descriptor update */ + dma_rmb(); + + skb = cur_p->skb; + cur_p->skb = NULL; + + /* skb could be NULL if a previous pass already received the + * packet for this slot in the ring, but failed to refill it + * with a newly allocated buffer. In this case, don't try to + * receive it again. + */ + if (likely(skb)) { + length = cur_p->app4 & 0x0000FFFF; + + phys = desc_get_phys_addr(lp, cur_p); + dma_unmap_single(lp->dev, phys, lp->max_frm_size, + DMA_FROM_DEVICE); + + skb_put(skb, length); + skb->protocol = eth_type_trans(skb, lp->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; + } + + napi_gro_receive(napi, skb); + + size += length; + packets++; + } + + new_skb = napi_alloc_skb(napi, lp->max_frm_size); + if (!new_skb) + break; + + phys = dma_map_single(lp->dev, new_skb->data, + lp->max_frm_size, + DMA_FROM_DEVICE); + if (unlikely(dma_mapping_error(lp->dev, phys))) { + if (net_ratelimit()) + netdev_err(lp->ndev, "RX DMA mapping error\n"); + dev_kfree_skb(new_skb); + break; + } + desc_set_phys_addr(lp, phys, cur_p); + + cur_p->cntrl = lp->max_frm_size; + cur_p->status = 0; + cur_p->skb = new_skb; + + /* Only update tail_p to mark this slot as usable after it has + * been successfully refilled. + */ + tail_p = lp->rx_bd_p + sizeof(*lp->rx_bd_v) * lp->rx_bd_ci; + + if (++lp->rx_bd_ci >= lp->rx_bd_num) + lp->rx_bd_ci = 0; + cur_p = &lp->rx_bd_v[lp->rx_bd_ci]; + } + + u64_stats_update_begin(&lp->rx_stat_sync); + u64_stats_add(&lp->rx_packets, packets); + u64_stats_add(&lp->rx_bytes, size); + u64_stats_update_end(&lp->rx_stat_sync); + + if (tail_p) + axienet_dma_out_addr(lp, XAXIDMA_RX_TDESC_OFFSET, tail_p); + + if (packets < budget && napi_complete_done(napi, packets)) { + /* Re-enable RX completion interrupts. This should + * cause an immediate interrupt if any RX packets are + * already pending. + */ + axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, lp->rx_dma_cr); + } + return packets; +} + +/** + * axienet_tx_irq - Tx Done Isr. + * @irq: irq number + * @_ndev: net_device pointer + * + * Return: IRQ_HANDLED if device generated a TX interrupt, IRQ_NONE otherwise. + * + * This is the Axi DMA Tx done Isr. It invokes NAPI polling to complete the + * TX BD processing. + */ +static irqreturn_t axienet_tx_irq(int irq, void *_ndev) +{ + 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_ALL_MASK)) + return IRQ_NONE; + + axienet_dma_out32(lp, XAXIDMA_TX_SR_OFFSET, status); + + if (unlikely(status & XAXIDMA_IRQ_ERROR_MASK)) { + netdev_err(ndev, "DMA Tx error 0x%x\n", status); + netdev_err(ndev, "Current BD is at: 0x%x%08x\n", + (lp->tx_bd_v[lp->tx_bd_ci]).phys_msb, + (lp->tx_bd_v[lp->tx_bd_ci]).phys); + schedule_work(&lp->dma_err_task); + } else { + /* Disable further TX completion interrupts and schedule + * NAPI to handle the completions. + */ + u32 cr = lp->tx_dma_cr; + + cr &= ~(XAXIDMA_IRQ_IOC_MASK | XAXIDMA_IRQ_DELAY_MASK); + axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, cr); + + napi_schedule(&lp->napi_tx); + } + + return IRQ_HANDLED; +} + +/** + * axienet_rx_irq - Rx Isr. + * @irq: irq number + * @_ndev: net_device pointer + * + * Return: IRQ_HANDLED if device generated a RX interrupt, IRQ_NONE otherwise. + * + * This is the Axi DMA Rx Isr. It invokes NAPI polling to complete the RX BD + * processing. + */ +static irqreturn_t axienet_rx_irq(int irq, void *_ndev) +{ + 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_ALL_MASK)) + return IRQ_NONE; + + axienet_dma_out32(lp, XAXIDMA_RX_SR_OFFSET, status); + + if (unlikely(status & XAXIDMA_IRQ_ERROR_MASK)) { + netdev_err(ndev, "DMA Rx error 0x%x\n", status); + netdev_err(ndev, "Current BD is at: 0x%x%08x\n", + (lp->rx_bd_v[lp->rx_bd_ci]).phys_msb, + (lp->rx_bd_v[lp->rx_bd_ci]).phys); + schedule_work(&lp->dma_err_task); + } else { + /* Disable further RX completion interrupts and schedule + * NAPI receive. + */ + u32 cr = lp->rx_dma_cr; + + cr &= ~(XAXIDMA_IRQ_IOC_MASK | XAXIDMA_IRQ_DELAY_MASK); + axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, cr); + + napi_schedule(&lp->napi_rx); + } + + return IRQ_HANDLED; +} + +/** + * axienet_eth_irq - Ethernet core Isr. + * @irq: irq number + * @_ndev: net_device pointer + * + * Return: IRQ_HANDLED if device generated a core interrupt, IRQ_NONE otherwise. + * + * Handle miscellaneous conditions indicated by Ethernet core IRQ. + */ +static irqreturn_t axienet_eth_irq(int irq, void *_ndev) +{ + struct net_device *ndev = _ndev; + struct axienet_local *lp = netdev_priv(ndev); + unsigned int pending; + + pending = axienet_ior(lp, XAE_IP_OFFSET); + if (!pending) + return IRQ_NONE; + + if (pending & XAE_INT_RXFIFOOVR_MASK) + ndev->stats.rx_missed_errors++; + + if (pending & XAE_INT_RXRJECT_MASK) + ndev->stats.rx_frame_errors++; + + axienet_iow(lp, XAE_IS_OFFSET, pending); + return IRQ_HANDLED; +} + +static void axienet_dma_err_handler(struct work_struct *work); + +/** + * axienet_open - Driver open routine. + * @ndev: Pointer to net_device structure + * + * Return: 0, on success. + * non-zero error value on failure + * + * This is the driver open routine. It calls phylink_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; + struct axienet_local *lp = netdev_priv(ndev); + + dev_dbg(&ndev->dev, "axienet_open()\n"); + + /* When we do an Axi Ethernet reset, it resets the complete core + * including the MDIO. MDIO must be disabled before resetting. + * Hold MDIO bus lock to avoid MDIO accesses during the reset. + */ + axienet_lock_mii(lp); + ret = axienet_device_reset(ndev); + axienet_unlock_mii(lp); + + ret = phylink_of_phy_connect(lp->phylink, lp->dev->of_node, 0); + if (ret) { + dev_err(lp->dev, "phylink_of_phy_connect() failed: %d\n", ret); + return ret; + } + + phylink_start(lp->phylink); + + /* Enable worker thread for Axi DMA error handling */ + INIT_WORK(&lp->dma_err_task, axienet_dma_err_handler); + + napi_enable(&lp->napi_rx); + napi_enable(&lp->napi_tx); + + /* Enable interrupts for Axi DMA Tx */ + ret = request_irq(lp->tx_irq, axienet_tx_irq, IRQF_SHARED, + ndev->name, ndev); + if (ret) + goto err_tx_irq; + /* Enable interrupts for Axi DMA Rx */ + ret = request_irq(lp->rx_irq, axienet_rx_irq, IRQF_SHARED, + ndev->name, ndev); + if (ret) + goto err_rx_irq; + /* Enable interrupts for Axi Ethernet core (if defined) */ + if (lp->eth_irq > 0) { + ret = request_irq(lp->eth_irq, axienet_eth_irq, IRQF_SHARED, + ndev->name, ndev); + if (ret) + goto err_eth_irq; + } + + return 0; + +err_eth_irq: + free_irq(lp->rx_irq, ndev); +err_rx_irq: + free_irq(lp->tx_irq, ndev); +err_tx_irq: + napi_disable(&lp->napi_tx); + napi_disable(&lp->napi_rx); + phylink_stop(lp->phylink); + phylink_disconnect_phy(lp->phylink); + cancel_work_sync(&lp->dma_err_task); + dev_err(lp->dev, "request_irq() failed\n"); + return ret; +} + +/** + * axienet_stop - Driver stop routine. + * @ndev: Pointer to net_device structure + * + * Return: 0, on success. + * + * This is the driver stop routine. It calls phylink_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) +{ + struct axienet_local *lp = netdev_priv(ndev); + + dev_dbg(&ndev->dev, "axienet_close()\n"); + + napi_disable(&lp->napi_tx); + napi_disable(&lp->napi_rx); + + phylink_stop(lp->phylink); + phylink_disconnect_phy(lp->phylink); + + axienet_setoptions(ndev, lp->options & + ~(XAE_OPTION_TXEN | XAE_OPTION_RXEN)); + + axienet_dma_stop(lp); + + axienet_iow(lp, XAE_IE_OFFSET, 0); + + cancel_work_sync(&lp->dma_err_task); + + if (lp->eth_irq > 0) + free_irq(lp->eth_irq, ndev); + free_irq(lp->tx_irq, ndev); + free_irq(lp->rx_irq, ndev); + + 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 + * + * Return: 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 ((new_mtu + VLAN_ETH_HLEN + + XAE_TRL_SIZE) > lp->rxmem) + 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 int axienet_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct axienet_local *lp = netdev_priv(dev); + + if (!netif_running(dev)) + return -EINVAL; + + return phylink_mii_ioctl(lp->phylink, rq, cmd); +} + +static void +axienet_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats) +{ + struct axienet_local *lp = netdev_priv(dev); + unsigned int start; + + netdev_stats_to_stats64(stats, &dev->stats); + + do { + start = u64_stats_fetch_begin_irq(&lp->rx_stat_sync); + stats->rx_packets = u64_stats_read(&lp->rx_packets); + stats->rx_bytes = u64_stats_read(&lp->rx_bytes); + } while (u64_stats_fetch_retry_irq(&lp->rx_stat_sync, start)); + + do { + start = u64_stats_fetch_begin_irq(&lp->tx_stat_sync); + stats->tx_packets = u64_stats_read(&lp->tx_packets); + stats->tx_bytes = u64_stats_read(&lp->tx_bytes); + } while (u64_stats_fetch_retry_irq(&lp->tx_stat_sync, start)); +} + +static const struct net_device_ops axienet_netdev_ops = { + .ndo_open = axienet_open, + .ndo_stop = axienet_stop, + .ndo_start_xmit = axienet_start_xmit, + .ndo_get_stats64 = axienet_get_stats64, + .ndo_change_mtu = axienet_change_mtu, + .ndo_set_mac_address = netdev_set_mac_address, + .ndo_validate_addr = eth_validate_addr, + .ndo_eth_ioctl = axienet_ioctl, + .ndo_set_rx_mode = axienet_set_multicast_list, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = axienet_poll_controller, +#endif +}; + +/** + * 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) +{ + strscpy(ed->driver, DRIVER_NAME, sizeof(ed->driver)); + strscpy(ed->version, DRIVER_VERSION, sizeof(ed->version)); +} + +/** + * 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. + * + * Return: the total regs length + */ +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[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); + data[32] = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); + data[33] = axienet_dma_in32(lp, XAXIDMA_TX_SR_OFFSET); + data[34] = axienet_dma_in32(lp, XAXIDMA_TX_CDESC_OFFSET); + data[35] = axienet_dma_in32(lp, XAXIDMA_TX_TDESC_OFFSET); + data[36] = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); + data[37] = axienet_dma_in32(lp, XAXIDMA_RX_SR_OFFSET); + data[38] = axienet_dma_in32(lp, XAXIDMA_RX_CDESC_OFFSET); + data[39] = axienet_dma_in32(lp, XAXIDMA_RX_TDESC_OFFSET); +} + +static void +axienet_ethtools_get_ringparam(struct net_device *ndev, + struct ethtool_ringparam *ering, + struct kernel_ethtool_ringparam *kernel_ering, + struct netlink_ext_ack *extack) +{ + struct axienet_local *lp = netdev_priv(ndev); + + ering->rx_max_pending = RX_BD_NUM_MAX; + ering->rx_mini_max_pending = 0; + ering->rx_jumbo_max_pending = 0; + ering->tx_max_pending = TX_BD_NUM_MAX; + ering->rx_pending = lp->rx_bd_num; + ering->rx_mini_pending = 0; + ering->rx_jumbo_pending = 0; + ering->tx_pending = lp->tx_bd_num; +} + +static int +axienet_ethtools_set_ringparam(struct net_device *ndev, + struct ethtool_ringparam *ering, + struct kernel_ethtool_ringparam *kernel_ering, + struct netlink_ext_ack *extack) +{ + struct axienet_local *lp = netdev_priv(ndev); + + if (ering->rx_pending > RX_BD_NUM_MAX || + ering->rx_mini_pending || + ering->rx_jumbo_pending || + ering->tx_pending < TX_BD_NUM_MIN || + ering->tx_pending > TX_BD_NUM_MAX) + return -EINVAL; + + if (netif_running(ndev)) + return -EBUSY; + + lp->rx_bd_num = ering->rx_pending; + lp->tx_bd_num = ering->tx_pending; + return 0; +} + +/** + * 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) +{ + struct axienet_local *lp = netdev_priv(ndev); + + phylink_ethtool_get_pauseparam(lp->phylink, epauseparm); +} + +/** + * axienet_ethtools_set_pauseparam - Set device pause parameter(flow control) + * settings. + * @ndev: Pointer to net_device structure + * @epauseparm: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. + * + * Return: 0 on success, -EFAULT if device is running + */ +static int +axienet_ethtools_set_pauseparam(struct net_device *ndev, + struct ethtool_pauseparam *epauseparm) +{ + struct axienet_local *lp = netdev_priv(ndev); + + return phylink_ethtool_set_pauseparam(lp->phylink, epauseparm); +} + +/** + * axienet_ethtools_get_coalesce - Get DMA interrupt coalescing count. + * @ndev: Pointer to net_device structure + * @ecoalesce: Pointer to ethtool_coalesce structure + * @kernel_coal: ethtool CQE mode setting structure + * @extack: extack for reporting error messages + * + * 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. + * + * Return: 0 always + */ +static int +axienet_ethtools_get_coalesce(struct net_device *ndev, + struct ethtool_coalesce *ecoalesce, + struct kernel_ethtool_coalesce *kernel_coal, + struct netlink_ext_ack *extack) +{ + struct axienet_local *lp = netdev_priv(ndev); + + ecoalesce->rx_max_coalesced_frames = lp->coalesce_count_rx; + ecoalesce->rx_coalesce_usecs = lp->coalesce_usec_rx; + ecoalesce->tx_max_coalesced_frames = lp->coalesce_count_tx; + ecoalesce->tx_coalesce_usecs = lp->coalesce_usec_tx; + return 0; +} + +/** + * axienet_ethtools_set_coalesce - Set DMA interrupt coalescing count. + * @ndev: Pointer to net_device structure + * @ecoalesce: Pointer to ethtool_coalesce structure + * @kernel_coal: ethtool CQE mode setting structure + * @extack: extack for reporting error messages + * + * 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. + * + * Return: 0, on success, Non-zero error value on failure. + */ +static int +axienet_ethtools_set_coalesce(struct net_device *ndev, + struct ethtool_coalesce *ecoalesce, + struct kernel_ethtool_coalesce *kernel_coal, + struct netlink_ext_ack *extack) +{ + struct axienet_local *lp = netdev_priv(ndev); + + if (netif_running(ndev)) { + netdev_err(ndev, + "Please stop netif before applying configuration\n"); + return -EFAULT; + } + + if (ecoalesce->rx_max_coalesced_frames) + lp->coalesce_count_rx = ecoalesce->rx_max_coalesced_frames; + if (ecoalesce->rx_coalesce_usecs) + lp->coalesce_usec_rx = ecoalesce->rx_coalesce_usecs; + if (ecoalesce->tx_max_coalesced_frames) + lp->coalesce_count_tx = ecoalesce->tx_max_coalesced_frames; + if (ecoalesce->tx_coalesce_usecs) + lp->coalesce_usec_tx = ecoalesce->tx_coalesce_usecs; + + return 0; +} + +static int +axienet_ethtools_get_link_ksettings(struct net_device *ndev, + struct ethtool_link_ksettings *cmd) +{ + struct axienet_local *lp = netdev_priv(ndev); + + return phylink_ethtool_ksettings_get(lp->phylink, cmd); +} + +static int +axienet_ethtools_set_link_ksettings(struct net_device *ndev, + const struct ethtool_link_ksettings *cmd) +{ + struct axienet_local *lp = netdev_priv(ndev); + + return phylink_ethtool_ksettings_set(lp->phylink, cmd); +} + +static int axienet_ethtools_nway_reset(struct net_device *dev) +{ + struct axienet_local *lp = netdev_priv(dev); + + return phylink_ethtool_nway_reset(lp->phylink); +} + +static const struct ethtool_ops axienet_ethtool_ops = { + .supported_coalesce_params = ETHTOOL_COALESCE_MAX_FRAMES | + ETHTOOL_COALESCE_USECS, + .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_ringparam = axienet_ethtools_get_ringparam, + .set_ringparam = axienet_ethtools_set_ringparam, + .get_pauseparam = axienet_ethtools_get_pauseparam, + .set_pauseparam = axienet_ethtools_set_pauseparam, + .get_coalesce = axienet_ethtools_get_coalesce, + .set_coalesce = axienet_ethtools_set_coalesce, + .get_link_ksettings = axienet_ethtools_get_link_ksettings, + .set_link_ksettings = axienet_ethtools_set_link_ksettings, + .nway_reset = axienet_ethtools_nway_reset, +}; + +static struct axienet_local *pcs_to_axienet_local(struct phylink_pcs *pcs) +{ + return container_of(pcs, struct axienet_local, pcs); +} + +static void axienet_pcs_get_state(struct phylink_pcs *pcs, + struct phylink_link_state *state) +{ + struct mdio_device *pcs_phy = pcs_to_axienet_local(pcs)->pcs_phy; + + phylink_mii_c22_pcs_get_state(pcs_phy, state); +} + +static void axienet_pcs_an_restart(struct phylink_pcs *pcs) +{ + struct mdio_device *pcs_phy = pcs_to_axienet_local(pcs)->pcs_phy; + + phylink_mii_c22_pcs_an_restart(pcs_phy); +} + +static int axienet_pcs_config(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + struct mdio_device *pcs_phy = pcs_to_axienet_local(pcs)->pcs_phy; + struct net_device *ndev = pcs_to_axienet_local(pcs)->ndev; + struct axienet_local *lp = netdev_priv(ndev); + int ret; + + if (lp->switch_x_sgmii) { + ret = mdiodev_write(pcs_phy, XLNX_MII_STD_SELECT_REG, + interface == PHY_INTERFACE_MODE_SGMII ? + XLNX_MII_STD_SELECT_SGMII : 0); + if (ret < 0) { + netdev_warn(ndev, + "Failed to switch PHY interface: %d\n", + ret); + return ret; + } + } + + ret = phylink_mii_c22_pcs_config(pcs_phy, mode, interface, advertising); + if (ret < 0) + netdev_warn(ndev, "Failed to configure PCS: %d\n", ret); + + return ret; +} + +static const struct phylink_pcs_ops axienet_pcs_ops = { + .pcs_get_state = axienet_pcs_get_state, + .pcs_config = axienet_pcs_config, + .pcs_an_restart = axienet_pcs_an_restart, +}; + +static struct phylink_pcs *axienet_mac_select_pcs(struct phylink_config *config, + phy_interface_t interface) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct axienet_local *lp = netdev_priv(ndev); + + if (interface == PHY_INTERFACE_MODE_1000BASEX || + interface == PHY_INTERFACE_MODE_SGMII) + return &lp->pcs; + + return NULL; +} + +static void axienet_mac_config(struct phylink_config *config, unsigned int mode, + const struct phylink_link_state *state) +{ + /* nothing meaningful to do */ +} + +static void axienet_mac_link_down(struct phylink_config *config, + unsigned int mode, + phy_interface_t interface) +{ + /* nothing meaningful to do */ +} + +static void axienet_mac_link_up(struct phylink_config *config, + struct phy_device *phy, + unsigned int mode, phy_interface_t interface, + int speed, int duplex, + bool tx_pause, bool rx_pause) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct axienet_local *lp = netdev_priv(ndev); + u32 emmc_reg, fcc_reg; + + emmc_reg = axienet_ior(lp, XAE_EMMC_OFFSET); + emmc_reg &= ~XAE_EMMC_LINKSPEED_MASK; + + switch (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); + + fcc_reg = axienet_ior(lp, XAE_FCC_OFFSET); + if (tx_pause) + fcc_reg |= XAE_FCC_FCTX_MASK; + else + fcc_reg &= ~XAE_FCC_FCTX_MASK; + if (rx_pause) + fcc_reg |= XAE_FCC_FCRX_MASK; + else + fcc_reg &= ~XAE_FCC_FCRX_MASK; + axienet_iow(lp, XAE_FCC_OFFSET, fcc_reg); +} + +static const struct phylink_mac_ops axienet_phylink_ops = { + .validate = phylink_generic_validate, + .mac_select_pcs = axienet_mac_select_pcs, + .mac_config = axienet_mac_config, + .mac_link_down = axienet_mac_link_down, + .mac_link_up = axienet_mac_link_up, +}; + +/** + * axienet_dma_err_handler - Work queue task for Axi DMA Error + * @work: pointer to work_struct + * + * Resets the Axi DMA and Axi Ethernet devices, and reconfigures the + * Tx/Rx BDs. + */ +static void axienet_dma_err_handler(struct work_struct *work) +{ + u32 i; + u32 axienet_status; + struct axidma_bd *cur_p; + struct axienet_local *lp = container_of(work, struct axienet_local, + dma_err_task); + struct net_device *ndev = lp->ndev; + + napi_disable(&lp->napi_tx); + napi_disable(&lp->napi_rx); + + axienet_setoptions(ndev, lp->options & + ~(XAE_OPTION_TXEN | XAE_OPTION_RXEN)); + + axienet_dma_stop(lp); + + for (i = 0; i < lp->tx_bd_num; i++) { + cur_p = &lp->tx_bd_v[i]; + if (cur_p->cntrl) { + dma_addr_t addr = desc_get_phys_addr(lp, cur_p); + + dma_unmap_single(lp->dev, addr, + (cur_p->cntrl & + XAXIDMA_BD_CTRL_LENGTH_MASK), + DMA_TO_DEVICE); + } + if (cur_p->skb) + dev_kfree_skb_irq(cur_p->skb); + cur_p->phys = 0; + cur_p->phys_msb = 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->skb = NULL; + } + + for (i = 0; i < lp->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; + + axienet_dma_start(lp); + + 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_IE_OFFSET, lp->eth_irq > 0 ? + XAE_INT_RECV_ERROR_MASK : 0); + 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); + napi_enable(&lp->napi_rx); + napi_enable(&lp->napi_tx); +} + +/** + * axienet_probe - Axi Ethernet probe function. + * @pdev: Pointer to platform device structure. + * + * Return: 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_probe(struct platform_device *pdev) +{ + int ret; + struct device_node *np; + struct axienet_local *lp; + struct net_device *ndev; + struct resource *ethres; + u8 mac_addr[ETH_ALEN]; + int addr_width = 32; + u32 value; + + ndev = alloc_etherdev(sizeof(*lp)); + if (!ndev) + return -ENOMEM; + + platform_set_drvdata(pdev, ndev); + + SET_NETDEV_DEV(ndev, &pdev->dev); + ndev->flags &= ~IFF_MULTICAST; /* clear multicast */ + ndev->features = NETIF_F_SG; + ndev->netdev_ops = &axienet_netdev_ops; + ndev->ethtool_ops = &axienet_ethtool_ops; + + /* MTU range: 64 - 9000 */ + ndev->min_mtu = 64; + ndev->max_mtu = XAE_JUMBO_MTU; + + lp = netdev_priv(ndev); + lp->ndev = ndev; + lp->dev = &pdev->dev; + lp->options = XAE_OPTION_DEFAULTS; + lp->rx_bd_num = RX_BD_NUM_DEFAULT; + lp->tx_bd_num = TX_BD_NUM_DEFAULT; + + u64_stats_init(&lp->rx_stat_sync); + u64_stats_init(&lp->tx_stat_sync); + + netif_napi_add(ndev, &lp->napi_rx, axienet_rx_poll); + netif_napi_add(ndev, &lp->napi_tx, axienet_tx_poll); + + lp->axi_clk = devm_clk_get_optional(&pdev->dev, "s_axi_lite_clk"); + if (!lp->axi_clk) { + /* For backward compatibility, if named AXI clock is not present, + * treat the first clock specified as the AXI clock. + */ + lp->axi_clk = devm_clk_get_optional(&pdev->dev, NULL); + } + if (IS_ERR(lp->axi_clk)) { + ret = PTR_ERR(lp->axi_clk); + goto free_netdev; + } + ret = clk_prepare_enable(lp->axi_clk); + if (ret) { + dev_err(&pdev->dev, "Unable to enable AXI clock: %d\n", ret); + goto free_netdev; + } + + lp->misc_clks[0].id = "axis_clk"; + lp->misc_clks[1].id = "ref_clk"; + lp->misc_clks[2].id = "mgt_clk"; + + ret = devm_clk_bulk_get_optional(&pdev->dev, XAE_NUM_MISC_CLOCKS, lp->misc_clks); + if (ret) + goto cleanup_clk; + + ret = clk_bulk_prepare_enable(XAE_NUM_MISC_CLOCKS, lp->misc_clks); + if (ret) + goto cleanup_clk; + + /* Map device registers */ + lp->regs = devm_platform_get_and_ioremap_resource(pdev, 0, ðres); + if (IS_ERR(lp->regs)) { + ret = PTR_ERR(lp->regs); + goto cleanup_clk; + } + lp->regs_start = ethres->start; + + /* Setup checksum offload, but default to off if not specified */ + lp->features = 0; + + ret = of_property_read_u32(pdev->dev.of_node, "xlnx,txcsum", &value); + if (!ret) { + switch (value) { + 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; + } + } + ret = of_property_read_u32(pdev->dev.of_node, "xlnx,rxcsum", &value); + if (!ret) { + switch (value) { + 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 large 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. + */ + of_property_read_u32(pdev->dev.of_node, "xlnx,rxmem", &lp->rxmem); + + lp->switch_x_sgmii = of_property_read_bool(pdev->dev.of_node, + "xlnx,switch-x-sgmii"); + + /* Start with the proprietary, and broken phy_type */ + ret = of_property_read_u32(pdev->dev.of_node, "xlnx,phy-type", &value); + if (!ret) { + netdev_warn(ndev, "Please upgrade your device tree binary blob to use phy-mode"); + switch (value) { + case XAE_PHY_TYPE_MII: + lp->phy_mode = PHY_INTERFACE_MODE_MII; + break; + case XAE_PHY_TYPE_GMII: + lp->phy_mode = PHY_INTERFACE_MODE_GMII; + break; + case XAE_PHY_TYPE_RGMII_2_0: + lp->phy_mode = PHY_INTERFACE_MODE_RGMII_ID; + break; + case XAE_PHY_TYPE_SGMII: + lp->phy_mode = PHY_INTERFACE_MODE_SGMII; + break; + case XAE_PHY_TYPE_1000BASE_X: + lp->phy_mode = PHY_INTERFACE_MODE_1000BASEX; + break; + default: + ret = -EINVAL; + goto cleanup_clk; + } + } else { + ret = of_get_phy_mode(pdev->dev.of_node, &lp->phy_mode); + if (ret) + goto cleanup_clk; + } + if (lp->switch_x_sgmii && lp->phy_mode != PHY_INTERFACE_MODE_SGMII && + lp->phy_mode != PHY_INTERFACE_MODE_1000BASEX) { + dev_err(&pdev->dev, "xlnx,switch-x-sgmii only supported with SGMII or 1000BaseX\n"); + ret = -EINVAL; + goto cleanup_clk; + } + + /* Find the DMA node, map the DMA registers, and decode the DMA IRQs */ + np = of_parse_phandle(pdev->dev.of_node, "axistream-connected", 0); + if (np) { + struct resource dmares; + + ret = of_address_to_resource(np, 0, &dmares); + if (ret) { + dev_err(&pdev->dev, + "unable to get DMA resource\n"); + of_node_put(np); + goto cleanup_clk; + } + lp->dma_regs = devm_ioremap_resource(&pdev->dev, + &dmares); + lp->rx_irq = irq_of_parse_and_map(np, 1); + lp->tx_irq = irq_of_parse_and_map(np, 0); + of_node_put(np); + lp->eth_irq = platform_get_irq_optional(pdev, 0); + } else { + /* Check for these resources directly on the Ethernet node. */ + lp->dma_regs = devm_platform_get_and_ioremap_resource(pdev, 1, NULL); + lp->rx_irq = platform_get_irq(pdev, 1); + lp->tx_irq = platform_get_irq(pdev, 0); + lp->eth_irq = platform_get_irq_optional(pdev, 2); + } + if (IS_ERR(lp->dma_regs)) { + dev_err(&pdev->dev, "could not map DMA regs\n"); + ret = PTR_ERR(lp->dma_regs); + goto cleanup_clk; + } + if ((lp->rx_irq <= 0) || (lp->tx_irq <= 0)) { + dev_err(&pdev->dev, "could not determine irqs\n"); + ret = -ENOMEM; + goto cleanup_clk; + } + + /* Reset core now that clocks are enabled, prior to accessing MDIO */ + ret = __axienet_device_reset(lp); + if (ret) + goto cleanup_clk; + + /* Autodetect the need for 64-bit DMA pointers. + * When the IP is configured for a bus width bigger than 32 bits, + * writing the MSB registers is mandatory, even if they are all 0. + * We can detect this case by writing all 1's to one such register + * and see if that sticks: when the IP is configured for 32 bits + * only, those registers are RES0. + * Those MSB registers were introduced in IP v7.1, which we check first. + */ + if ((axienet_ior(lp, XAE_ID_OFFSET) >> 24) >= 0x9) { + void __iomem *desc = lp->dma_regs + XAXIDMA_TX_CDESC_OFFSET + 4; + + iowrite32(0x0, desc); + if (ioread32(desc) == 0) { /* sanity check */ + iowrite32(0xffffffff, desc); + if (ioread32(desc) > 0) { + lp->features |= XAE_FEATURE_DMA_64BIT; + addr_width = 64; + dev_info(&pdev->dev, + "autodetected 64-bit DMA range\n"); + } + iowrite32(0x0, desc); + } + } + if (!IS_ENABLED(CONFIG_64BIT) && lp->features & XAE_FEATURE_DMA_64BIT) { + dev_err(&pdev->dev, "64-bit addressable DMA is not compatible with 32-bit archecture\n"); + ret = -EINVAL; + goto cleanup_clk; + } + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(addr_width)); + if (ret) { + dev_err(&pdev->dev, "No suitable DMA available\n"); + goto cleanup_clk; + } + + /* Check for Ethernet core IRQ (optional) */ + if (lp->eth_irq <= 0) + dev_info(&pdev->dev, "Ethernet core IRQ not defined\n"); + + /* Retrieve the MAC address */ + ret = of_get_mac_address(pdev->dev.of_node, mac_addr); + if (!ret) { + axienet_set_mac_address(ndev, mac_addr); + } else { + dev_warn(&pdev->dev, "could not find MAC address property: %d\n", + ret); + axienet_set_mac_address(ndev, NULL); + } + + lp->coalesce_count_rx = XAXIDMA_DFT_RX_THRESHOLD; + lp->coalesce_usec_rx = XAXIDMA_DFT_RX_USEC; + lp->coalesce_count_tx = XAXIDMA_DFT_TX_THRESHOLD; + lp->coalesce_usec_tx = XAXIDMA_DFT_TX_USEC; + + ret = axienet_mdio_setup(lp); + if (ret) + dev_warn(&pdev->dev, + "error registering MDIO bus: %d\n", ret); + + if (lp->phy_mode == PHY_INTERFACE_MODE_SGMII || + lp->phy_mode == PHY_INTERFACE_MODE_1000BASEX) { + np = of_parse_phandle(pdev->dev.of_node, "pcs-handle", 0); + if (!np) { + /* Deprecated: Always use "pcs-handle" for pcs_phy. + * Falling back to "phy-handle" here is only for + * backward compatibility with old device trees. + */ + np = of_parse_phandle(pdev->dev.of_node, "phy-handle", 0); + } + if (!np) { + dev_err(&pdev->dev, "pcs-handle (preferred) or phy-handle required for 1000BaseX/SGMII\n"); + ret = -EINVAL; + goto cleanup_mdio; + } + lp->pcs_phy = of_mdio_find_device(np); + if (!lp->pcs_phy) { + ret = -EPROBE_DEFER; + of_node_put(np); + goto cleanup_mdio; + } + of_node_put(np); + lp->pcs.ops = &axienet_pcs_ops; + lp->pcs.poll = true; + } + + lp->phylink_config.dev = &ndev->dev; + lp->phylink_config.type = PHYLINK_NETDEV; + lp->phylink_config.mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | + MAC_10FD | MAC_100FD | MAC_1000FD; + + __set_bit(lp->phy_mode, lp->phylink_config.supported_interfaces); + if (lp->switch_x_sgmii) { + __set_bit(PHY_INTERFACE_MODE_1000BASEX, + lp->phylink_config.supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_SGMII, + lp->phylink_config.supported_interfaces); + } + + lp->phylink = phylink_create(&lp->phylink_config, pdev->dev.fwnode, + lp->phy_mode, + &axienet_phylink_ops); + if (IS_ERR(lp->phylink)) { + ret = PTR_ERR(lp->phylink); + dev_err(&pdev->dev, "phylink_create error (%i)\n", ret); + goto cleanup_mdio; + } + + ret = register_netdev(lp->ndev); + if (ret) { + dev_err(lp->dev, "register_netdev() error (%i)\n", ret); + goto cleanup_phylink; + } + + return 0; + +cleanup_phylink: + phylink_destroy(lp->phylink); + +cleanup_mdio: + if (lp->pcs_phy) + put_device(&lp->pcs_phy->dev); + if (lp->mii_bus) + axienet_mdio_teardown(lp); +cleanup_clk: + clk_bulk_disable_unprepare(XAE_NUM_MISC_CLOCKS, lp->misc_clks); + clk_disable_unprepare(lp->axi_clk); + +free_netdev: + free_netdev(ndev); + + return ret; +} + +static int axienet_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct axienet_local *lp = netdev_priv(ndev); + + unregister_netdev(ndev); + + if (lp->phylink) + phylink_destroy(lp->phylink); + + if (lp->pcs_phy) + put_device(&lp->pcs_phy->dev); + + axienet_mdio_teardown(lp); + + clk_bulk_disable_unprepare(XAE_NUM_MISC_CLOCKS, lp->misc_clks); + clk_disable_unprepare(lp->axi_clk); + + free_netdev(ndev); + + return 0; +} + +static void axienet_shutdown(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + + rtnl_lock(); + netif_device_detach(ndev); + + if (netif_running(ndev)) + dev_close(ndev); + + rtnl_unlock(); +} + +static struct platform_driver axienet_driver = { + .probe = axienet_probe, + .remove = axienet_remove, + .shutdown = axienet_shutdown, + .driver = { + .name = "xilinx_axienet", + .of_match_table = axienet_of_match, + }, +}; + +module_platform_driver(axienet_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 000000000..0b3b6935c --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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) 2019 SED Systems, a division of Calian Ltd. + * Copyright (c) 2010 - 2012 Xilinx, Inc. All rights reserved. + */ + +#include <linux/clk.h> +#include <linux/of_address.h> +#include <linux/of_mdio.h> +#include <linux/jiffies.h> +#include <linux/iopoll.h> + +#include "xilinx_axienet.h" + +#define MAX_MDIO_FREQ 2500000 /* 2.5 MHz */ +#define DEFAULT_HOST_CLOCK 150000000 /* 150 MHz */ + +/* Wait till MDIO interface is ready to accept a new transaction.*/ +static int axienet_mdio_wait_until_ready(struct axienet_local *lp) +{ + u32 val; + + return readx_poll_timeout(axinet_ior_read_mcr, lp, + val, val & XAE_MDIO_MCR_READY_MASK, + 1, 20000); +} + +/* Enable the MDIO MDC. Called prior to a read/write operation */ +static void axienet_mdio_mdc_enable(struct axienet_local *lp) +{ + axienet_iow(lp, XAE_MDIO_MC_OFFSET, + ((u32)lp->mii_clk_div | XAE_MDIO_MC_MDIOEN_MASK)); +} + +/* Disable the MDIO MDC. Called after a read/write operation*/ +static void axienet_mdio_mdc_disable(struct axienet_local *lp) +{ + u32 mc_reg; + + mc_reg = axienet_ior(lp, XAE_MDIO_MC_OFFSET); + axienet_iow(lp, XAE_MDIO_MC_OFFSET, + (mc_reg & ~XAE_MDIO_MC_MDIOEN_MASK)); +} + +/** + * 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 + * + * Return: 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; + + axienet_mdio_mdc_enable(lp); + + ret = axienet_mdio_wait_until_ready(lp); + if (ret < 0) { + axienet_mdio_mdc_disable(lp); + 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) { + axienet_mdio_mdc_disable(lp); + 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); + + axienet_mdio_mdc_disable(lp); + 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 + * + * Return: 0 on success, -ETIMEDOUT on a timeout + * + * Writes the value to the requested register by first writing the value + * into MWD register. 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); + + axienet_mdio_mdc_enable(lp); + + ret = axienet_mdio_wait_until_ready(lp); + if (ret < 0) { + axienet_mdio_mdc_disable(lp); + 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) { + axienet_mdio_mdc_disable(lp); + return ret; + } + axienet_mdio_mdc_disable(lp); + return 0; +} + +/** + * axienet_mdio_enable - MDIO hardware setup function + * @lp: Pointer to axienet local data structure. + * + * Return: 0 on success, -ETIMEDOUT on a timeout. + * + * Sets up the MDIO interface by initializing the MDIO clock and enabling the + * MDIO interface in hardware. + **/ +int axienet_mdio_enable(struct axienet_local *lp) +{ + u32 host_clock; + + lp->mii_clk_div = 0; + + if (lp->axi_clk) { + host_clock = clk_get_rate(lp->axi_clk); + } else { + struct device_node *np1; + + /* Legacy fallback: detect CPU clock frequency and use as AXI + * bus clock frequency. This only works on certain platforms. + */ + np1 = of_find_node_by_name(NULL, "cpu"); + if (!np1) { + netdev_warn(lp->ndev, "Could not find CPU device node.\n"); + host_clock = DEFAULT_HOST_CLOCK; + } else { + int ret = of_property_read_u32(np1, "clock-frequency", + &host_clock); + if (ret) { + netdev_warn(lp->ndev, "CPU clock-frequency property not found.\n"); + host_clock = DEFAULT_HOST_CLOCK; + } + of_node_put(np1); + } + netdev_info(lp->ndev, "Setting assumed host clock to %u\n", + host_clock); + } + + /* 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 + */ + + lp->mii_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)) + lp->mii_clk_div++; + + netdev_dbg(lp->ndev, + "Setting MDIO clock divisor to %u/%u Hz host clock.\n", + lp->mii_clk_div, host_clock); + + axienet_iow(lp, XAE_MDIO_MC_OFFSET, lp->mii_clk_div | XAE_MDIO_MC_MDIOEN_MASK); + + return axienet_mdio_wait_until_ready(lp); +} + +/** + * axienet_mdio_disable - MDIO hardware disable function + * @lp: Pointer to axienet local data structure. + * + * Disable the MDIO interface in hardware. + **/ +void axienet_mdio_disable(struct axienet_local *lp) +{ + axienet_iow(lp, XAE_MDIO_MC_OFFSET, 0); +} + +/** + * axienet_mdio_setup - MDIO setup function + * @lp: Pointer to axienet local data structure. + * + * Return: 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. + * Register the MDIO interface. + **/ +int axienet_mdio_setup(struct axienet_local *lp) +{ + struct device_node *mdio_node; + struct mii_bus *bus; + int ret; + + ret = axienet_mdio_enable(lp); + if (ret < 0) + return ret; + + bus = mdiobus_alloc(); + if (!bus) + return -ENOMEM; + + snprintf(bus->id, MII_BUS_ID_SIZE, "axienet-%.8llx", + (unsigned long long)lp->regs_start); + + bus->priv = lp; + bus->name = "Xilinx Axi Ethernet MDIO"; + bus->read = axienet_mdio_read; + bus->write = axienet_mdio_write; + bus->parent = lp->dev; + lp->mii_bus = bus; + + mdio_node = of_get_child_by_name(lp->dev->of_node, "mdio"); + ret = of_mdiobus_register(bus, mdio_node); + of_node_put(mdio_node); + if (ret) { + mdiobus_free(bus); + lp->mii_bus = NULL; + return ret; + } + axienet_mdio_mdc_disable(lp); + 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); + 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 000000000..ad2c30d9a --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_emaclite.c @@ -0,0 +1,1272 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* 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>. + * + * Copyright (c) 2007 - 2013 Xilinx, Inc. + */ + +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/ethtool.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> +#include <linux/iopoll.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. */ + +#ifdef __BIG_ENDIAN +#define xemaclite_readl ioread32be +#define xemaclite_writel iowrite32be +#else +#define xemaclite_readl ioread32 +#define xemaclite_writel iowrite32 +#endif + +/** + * 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 to serialize xmit and tx_timeout execution + * @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 + * @last_link: last link status + */ +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; /* serialize xmit and tx_timeout execution */ + struct sk_buff *deferred_skb; + + struct phy_device *phy_dev; + struct device_node *phy_node; + + struct mii_bus *mii_bus; + + int last_link; +}; + +/*************************/ +/* 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 = xemaclite_readl(drvdata->base_addr + XEL_TSR_OFFSET); + xemaclite_writel(reg_data | XEL_TSR_XMIT_IE_MASK, + drvdata->base_addr + XEL_TSR_OFFSET); + + /* Enable the Rx interrupts for the first buffer */ + xemaclite_writel(XEL_RSR_RECV_IE_MASK, drvdata->base_addr + XEL_RSR_OFFSET); + + /* Enable the Global Interrupt Enable */ + xemaclite_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 */ + xemaclite_writel(XEL_GIER_GIE_MASK, drvdata->base_addr + XEL_GIER_OFFSET); + + /* Disable the Tx interrupts for the first buffer */ + reg_data = xemaclite_readl(drvdata->base_addr + XEL_TSR_OFFSET); + xemaclite_writel(reg_data & (~XEL_TSR_XMIT_IE_MASK), + drvdata->base_addr + XEL_TSR_OFFSET); + + /* Disable the Rx interrupts for the first buffer */ + reg_data = xemaclite_readl(drvdata->base_addr + XEL_RSR_OFFSET); + xemaclite_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(const void *src_ptr, u32 *dest_ptr, + unsigned int length) +{ + const u16 *from_u16_ptr; + u32 align_buffer; + u32 *to_u32_ptr; + u16 *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 int 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 = xemaclite_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 *)((uintptr_t __force)addr ^ + XEL_BUFFER_OFFSET); + reg_data = xemaclite_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); + + xemaclite_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 = xemaclite_readl(addr + XEL_TSR_OFFSET); + reg_data |= (XEL_TSR_XMIT_BUSY_MASK | XEL_TSR_XMIT_ACTIVE_MASK); + xemaclite_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 + * @maxlen: Maximum supported ethernet packet length + * + * 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, int maxlen) +{ + 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 = xemaclite_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 *) + ((uintptr_t __force)addr ^ + XEL_BUFFER_OFFSET); + else + return 0; /* No data was available */ + + /* Verify that buffer has valid data */ + reg_data = xemaclite_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(xemaclite_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_DATA_LEN) { + if (proto_type == ETH_P_IP) { + length = ((ntohl(xemaclite_readl(addr + + XEL_HEADER_IP_LENGTH_OFFSET + + XEL_RXBUFF_OFFSET)) >> + XEL_HEADER_SHIFT) & + XEL_RPLR_LENGTH_MASK); + length = min_t(u16, length, ETH_DATA_LEN); + 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; + } + + if (WARN_ON(length > maxlen)) + length = maxlen; + + /* Read from the EmacLite device */ + xemaclite_aligned_read((u32 __force *)(addr + XEL_RXBUFF_OFFSET), + data, length); + + /* Acknowledge the frame */ + reg_data = xemaclite_readl(addr + XEL_RSR_OFFSET); + reg_data &= ~XEL_RSR_RECV_DONE_MASK; + xemaclite_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, + const 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); + + xemaclite_writel(ETH_ALEN, addr + XEL_TPLR_OFFSET); + + /* Update the MAC address in the EmacLite */ + reg_data = xemaclite_readl(addr + XEL_TSR_OFFSET); + xemaclite_writel(reg_data | XEL_TSR_PROG_MAC_ADDR, addr + XEL_TSR_OFFSET); + + /* Wait for EmacLite to finish with the MAC address update */ + while ((xemaclite_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 + * @address: Void pointer to the sockaddr structure + * + * This function copies the HW address from the sockaddr structure 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; + + eth_hw_addr_set(dev, addr->sa_data); + xemaclite_update_address(lp, dev->dev_addr); + return 0; +} + +/** + * xemaclite_tx_timeout - Callback for Tx Timeout + * @dev: Pointer to the network device + * @txqueue: Unused + * + * This function is called when Tx time out occurs for Emaclite device. + */ +static void xemaclite_tx_timeout(struct net_device *dev, unsigned int txqueue) +{ + 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_irq(lp->deferred_skb); + lp->deferred_skb = NULL; + dev->stats.tx_errors++; + } + + /* To exclude tx timeout */ + netif_trans_update(dev); /* 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) + return; + + if (xemaclite_send_data(lp, (u8 *)lp->deferred_skb->data, + lp->deferred_skb->len)) + return; + + dev->stats.tx_bytes += lp->deferred_skb->len; + dev_consume_skb_irq(lp->deferred_skb); + lp->deferred_skb = NULL; + netif_trans_update(dev); /* 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; + u32 len; + + len = ETH_FRAME_LEN + ETH_FCS_LEN; + skb = netdev_alloc_skb(dev, len + NET_IP_ALIGN); + if (!skb) { + /* Couldn't get memory. */ + dev->stats.rx_dropped++; + dev_err(&lp->ndev->dev, "Could not allocate receive buffer\n"); + return; + } + + skb_reserve(skb, NET_IP_ALIGN); + + len = xemaclite_recv_data(lp, (u8 *)skb->data, len); + + 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 + * + * Return: IRQ_HANDLED + * + * 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 ((xemaclite_readl(base_addr + XEL_RSR_OFFSET) & + XEL_RSR_RECV_DONE_MASK) || + (xemaclite_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 = xemaclite_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; + xemaclite_writel(tx_status, base_addr + XEL_TSR_OFFSET); + + tx_complete = true; + } + + /* Check if the Transmission for the second buffer is completed */ + tx_status = xemaclite_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; + xemaclite_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) +{ + u32 val; + + /* wait for the MDIO interface to not be busy or timeout + * after some time. + */ + return readx_poll_timeout(xemaclite_readl, + lp->base_addr + XEL_MDIOCTRL_OFFSET, + val, !(val & XEL_MDIOCTRL_MDIOSTS_MASK), + 1000, 20000); +} + +/** + * 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 = xemaclite_readl(lp->base_addr + XEL_MDIOCTRL_OFFSET); + xemaclite_writel(XEL_MDIOADDR_OP_MASK | + ((phy_id << XEL_MDIOADDR_PHYADR_SHIFT) | reg), + lp->base_addr + XEL_MDIOADDR_OFFSET); + xemaclite_writel(ctrl_reg | XEL_MDIOCTRL_MDIOSTS_MASK, + lp->base_addr + XEL_MDIOCTRL_OFFSET); + + if (xemaclite_mdio_wait(lp)) + return -ETIMEDOUT; + + rc = xemaclite_readl(lp->base_addr + XEL_MDIORD_OFFSET); + + dev_dbg(&lp->ndev->dev, + "%s(phy_id=%i, reg=%x) == %x\n", __func__, + 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. + * + * Return: 0 upon success or a negative error upon failure + */ +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, + "%s(phy_id=%i, reg=%x, val=%x)\n", __func__, + 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 = xemaclite_readl(lp->base_addr + XEL_MDIOCTRL_OFFSET); + xemaclite_writel(~XEL_MDIOADDR_OP_MASK & + ((phy_id << XEL_MDIOADDR_PHYADR_SHIFT) | reg), + lp->base_addr + XEL_MDIOADDR_OFFSET); + xemaclite_writel(val, lp->base_addr + XEL_MDIOWR_OFFSET); + xemaclite_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 + * @dev: 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; + struct resource res; + struct device_node *np = of_get_parent(lp->phy_node); + struct device_node *npp; + int rc, ret; + + /* 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); + ret = of_address_to_resource(npp, 0, &res); + of_node_put(npp); + if (ret) { + dev_err(dev, "%s resource error!\n", + dev->of_node->full_name); + of_node_put(np); + return ret; + } + 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"); + else + put_device(&phydev->mdio.dev); + of_node_put(np); + return 0; + } + + /* Enable the MDIO bus by asserting the enable bit in MDIO Control + * register. + */ + xemaclite_writel(XEL_MDIOCTRL_MDIOEN_MASK, + lp->base_addr + XEL_MDIOCTRL_OFFSET); + + bus = mdiobus_alloc(); + if (!bus) { + dev_err(dev, "Failed to allocate mdiobus\n"); + of_node_put(np); + 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; + + rc = of_mdiobus_register(bus, np); + of_node_put(np); + if (rc) { + dev_err(dev, "Failed to register mdio bus.\n"); + goto err_register; + } + + lp->mii_bus = bus; + + 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. + * + * Return: 0 on success. -ENODEV, if PHY cannot be connected. + * Non-zero error value on failure. + */ +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) { + 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 */ + phy_set_max_speed(lp->phy_dev, SPEED_100); + 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. + * + * Return: 0, always. + */ +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: NETDEV_TX_OK, always. + */ +static netdev_tx_t +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 NETDEV_TX_OK; + } + spin_unlock_irqrestore(&lp->reset_lock, flags); + + skb_tx_timestamp(new_skb); + + dev->stats.tx_bytes += len; + dev_consume_skb_any(new_skb); + + return NETDEV_TX_OK; +} + +/** + * 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) { + dev_warn(&ofdev->dev, "Parameter %s not found, defaulting to false\n", s); + return false; + } + + return (bool)*p; +} + +/** + * xemaclite_ethtools_get_drvinfo - Get various Axi Emac Lite driver info + * @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 xemaclite_ethtools_get_drvinfo(struct net_device *ndev, + struct ethtool_drvinfo *ed) +{ + strscpy(ed->driver, DRIVER_NAME, sizeof(ed->driver)); +} + +static const struct ethtool_ops xemaclite_ethtool_ops = { + .get_drvinfo = xemaclite_ethtools_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_link_ksettings = phy_ethtool_get_link_ksettings, + .set_link_ksettings = phy_ethtool_set_link_ksettings, +}; + +static const struct net_device_ops xemaclite_netdev_ops; + +/** + * xemaclite_of_probe - Probe method for the Emaclite device. + * @ofdev: Pointer to OF device structure + * + * 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; + + 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 */ + rc = platform_get_irq(ofdev, 0); + if (rc < 0) + goto error; + + ndev->irq = rc; + + 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"); + + rc = of_get_ethdev_address(ofdev->dev.of_node, ndev); + if (rc) { + dev_warn(dev, "No MAC address found, using random\n"); + eth_hw_addr_random(ndev); + } + + /* Clear the Tx CSR's in case this is a restart */ + xemaclite_writel(0, lp->base_addr + XEL_TSR_OFFSET); + xemaclite_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); + xemaclite_mdio_setup(lp, &ofdev->dev); + + dev_info(dev, "MAC address is now %pM\n", ndev->dev_addr); + + ndev->netdev_ops = &xemaclite_netdev_ops; + ndev->ethtool_ops = &xemaclite_ethtool_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 put_node; + } + + dev_info(dev, + "Xilinx EmacLite at 0x%08lX mapped to 0x%p, irq=%d\n", + (unsigned long __force)ndev->mem_start, lp->base_addr, ndev->irq); + return 0; + +put_node: + of_node_put(lp->phy_node); +error: + free_netdev(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->mii_bus) { + mdiobus_unregister(lp->mii_bus); + mdiobus_free(lp->mii_bus); + lp->mii_bus = NULL; + } + + unregister_netdev(ndev); + + of_node_put(lp->phy_node); + lp->phy_node = NULL; + + free_netdev(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 + +/* Ioctl MII Interface */ +static int xemaclite_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + if (!dev->phydev || !netif_running(dev)) + return -EINVAL; + + switch (cmd) { + case SIOCGMIIPHY: + case SIOCGMIIREG: + case SIOCSMIIREG: + return phy_mii_ioctl(dev->phydev, rq, cmd); + default: + return -EOPNOTSUPP; + } +} + +static const 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, + .ndo_eth_ioctl = xemaclite_ioctl, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = xemaclite_poll_controller, +#endif +}; + +/* Match table for OF platform binding */ +static const 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, + .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"); |