From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.8 required=3.0 tests=DKIM_ADSP_CUSTOM_MED, FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH,MAILING_LIST_MULTI,SIGNED_OFF_BY,SPF_HELO_NONE,SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id D999CC19759 for ; Thu, 1 Aug 2019 07:28:20 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 8A8DE206A2 for ; Thu, 1 Aug 2019 07:28:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730893AbfHAH2T (ORCPT ); Thu, 1 Aug 2019 03:28:19 -0400 Received: from 212.199.177.27.static.012.net.il ([212.199.177.27]:40272 "EHLO herzl.nuvoton.co.il" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727460AbfHAH2R (ORCPT ); Thu, 1 Aug 2019 03:28:17 -0400 Received: from taln60.nuvoton.co.il (ntil-fw [212.199.177.25]) by herzl.nuvoton.co.il (8.13.8/8.13.8) with ESMTP id x717R6IG004916; Thu, 1 Aug 2019 10:27:06 +0300 Received: by taln60.nuvoton.co.il (Postfix, from userid 8441) id 4B2C662CAA; Thu, 1 Aug 2019 10:27:06 +0300 (IDT) From: Avi Fishman To: venture@google.com, yuenn@google.com, benjaminfair@google.com, davem@davemloft.net, robh+dt@kernel.org, mark.rutland@arm.com, gregkh@linuxfoundation.org Cc: avifishman70@gmail.com, tmaimon77@gmail.com, tali.perry1@gmail.com, openbmc@lists.ozlabs.org, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, tglx@linutronix.de Subject: [PATCH v1 2/2] net: npcm: add NPCM7xx EMC 10/100 Ethernet driver Date: Thu, 1 Aug 2019 10:26:11 +0300 Message-Id: <20190801072611.27935-3-avifishman70@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20190801072611.27935-1-avifishman70@gmail.com> References: <20190801072611.27935-1-avifishman70@gmail.com> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org EMC Ethernet Media Access Controller supports 10/100 Mbps and RMII. This driver has been working on Nuvoton BMC NPCM7xx. Signed-off-by: Avi Fishman --- drivers/net/ethernet/nuvoton/Kconfig | 21 +- drivers/net/ethernet/nuvoton/Makefile | 2 + drivers/net/ethernet/nuvoton/npcm7xx_emc.c | 2073 ++++++++++++++++++++ 3 files changed, 2093 insertions(+), 3 deletions(-) create mode 100644 drivers/net/ethernet/nuvoton/npcm7xx_emc.c diff --git a/drivers/net/ethernet/nuvoton/Kconfig b/drivers/net/ethernet/nuvoton/Kconfig index 325e26c549f8..3820dd3bc4b7 100644 --- a/drivers/net/ethernet/nuvoton/Kconfig +++ b/drivers/net/ethernet/nuvoton/Kconfig @@ -6,8 +6,8 @@ config NET_VENDOR_NUVOTON bool "Nuvoton devices" default y - depends on ARM && ARCH_W90X900 - ---help--- + depends on ARM && (ARCH_W90X900 || ARCH_NPCM7XX) + 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 @@ -22,8 +22,23 @@ config W90P910_ETH depends on ARM && ARCH_W90X900 select PHYLIB select MII - ---help--- + help Say Y here if you want to use built-in Ethernet ports on w90p910 processor. +config NPCM7XX_EMC_ETH + bool "Nuvoton NPCM7XX Ethernet EMC" + depends on ARM && ARCH_NPCM7XX + select PHYLIB + select MII + help + Say Y here if you want to use built-in Ethernet MAC + on NPCM750 MCU. + +config NPCM7XX_EMC_ETH_DEBUG + bool "Nuvoton NPCM7XX Ethernet EMC debug" + depends on NPCM7XX_EMC_ETH + help + Say Y here if you want debug info via /proc/driver/npcm7xx_emc.x + endif # NET_VENDOR_NUVOTON diff --git a/drivers/net/ethernet/nuvoton/Makefile b/drivers/net/ethernet/nuvoton/Makefile index 66f6e728d54b..e32be694e987 100644 --- a/drivers/net/ethernet/nuvoton/Makefile +++ b/drivers/net/ethernet/nuvoton/Makefile @@ -3,4 +3,6 @@ # Makefile for the Nuvoton network device drivers. # +#Eternet 10/100 EMC obj-$(CONFIG_W90P910_ETH) += w90p910_ether.o +obj-$(CONFIG_NPCM7XX_EMC_ETH) += npcm7xx_emc.o diff --git a/drivers/net/ethernet/nuvoton/npcm7xx_emc.c b/drivers/net/ethernet/nuvoton/npcm7xx_emc.c new file mode 100644 index 000000000000..de56c7192b76 --- /dev/null +++ b/drivers/net/ethernet/nuvoton/npcm7xx_emc.c @@ -0,0 +1,2073 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2014-2019 Nuvoton Technology corporation. + +#ifdef CONFIG_NPCM7XX_EMC_ETH_DEBUG +#define DEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#ifdef CONFIG_DEBUG_FS +static struct dentry *npcm7xx_fs_dir; +#endif + +#define MFSEL1_OFFSET 0x00C +#define MFSEL3_OFFSET 0x064 +#define INTCR_OFFSET 0x03C + +#define IPSRST1_OFFSET 0x020 + +#define DRV_MODULE_NAME "npcm7xx-emc" +#define DRV_MODULE_VERSION "3.90" + +/* Ethernet MAC Registers */ +#define REG_CAMCMR 0x00 +#define REG_CAMEN 0x04 +#define REG_CAMM_BASE 0x08 +#define REG_CAML_BASE 0x0c +#define REG_TXDLSA 0x88 +#define REG_RXDLSA 0x8C +#define REG_MCMDR 0x90 +#define REG_MIID 0x94 +#define REG_MIIDA 0x98 +#define REG_FFTCR 0x9C +#define REG_TSDR 0xa0 +#define REG_RSDR 0xa4 +#define REG_DMARFC 0xa8 +#define REG_MIEN 0xac +#define REG_MISTA 0xb0 +#define REG_MGSTA 0xb4 +#define REG_MPCNT 0xb8 +#define REG_MRPC 0xbc +#define REG_MRPCC 0xc0 +#define REG_MREPC 0xc4 +#define REG_DMARFS 0xc8 +#define REG_CTXDSA 0xcc +#define REG_CTXBSA 0xd0 +#define REG_CRXDSA 0xd4 +#define REG_CRXBSA 0xd8 + +/* EMC Diagnostic Registers */ +#define REG_RXFSM 0x200 +#define REG_TXFSM 0x204 +#define REG_FSM0 0x208 +#define REG_FSM1 0x20c +#define REG_DCR 0x210 +#define REG_DMMIR 0x214 +#define REG_BISTR 0x300 + +/* mac controller bit */ +#define MCMDR_RXON BIT(0) +#define MCMDR_ALP BIT(1) +#define MCMDR_ACP BIT(3) +#define MCMDR_SPCRC BIT(5) +#define MCMDR_TXON BIT(8) +#define MCMDR_NDEF BIT(9) +#define MCMDR_FDUP BIT(18) +#define MCMDR_ENMDC BIT(19) +#define MCMDR_OPMOD BIT(20) +#define SWR BIT(24) + +/* cam command regiser */ +#define CAMCMR_AUP BIT(0) +#define CAMCMR_AMP BIT(1) +#define CAMCMR_ABP BIT(2) +#define CAMCMR_CCAM BIT(3) +#define CAMCMR_ECMP BIT(4) + +/* cam enable regiser */ +#define CAM0EN BIT(0) + +/* mac mii controller bit */ +#define PHYAD BIT(8) +#define PHYWR BIT(16) +#define PHYBUSY BIT(17) +#define PHYPRESP BIT(18) +#define MDCON BIT(19) +#define CAM_ENTRY_SIZE 0x08 + +/* rx and tx status */ +#define TXDS_TXCP BIT(19) +#define RXDS_CRCE BIT(17) +#define RXDS_PTLE BIT(19) +#define RXDS_RXGD BIT(20) +#define RXDS_ALIE BIT(21) +#define RXDS_RP BIT(22) + +/* mac interrupt status*/ +#define MISTA_RXINTR BIT(0) +#define MISTA_CRCE BIT(1) +#define MISTA_RXOV BIT(2) +#define MISTA_PTLE BIT(3) +#define MISTA_RXGD BIT(4) +#define MISTA_ALIE BIT(5) +#define MISTA_RP BIT(6) +#define MISTA_MMP BIT(7) +#define MISTA_DFOI BIT(8) +#define MISTA_DENI BIT(9) +#define MISTA_RDU BIT(10) +#define MISTA_RXBERR BIT(11) +#define MISTA_CFR BIT(14) +#define MISTA_TXINTR BIT(16) +#define MISTA_TXEMP BIT(17) +#define MISTA_TXCP BIT(18) +#define MISTA_EXDEF BIT(19) +#define MISTA_NCS BIT(20) +#define MISTA_TXABT BIT(21) +#define MISTA_LC BIT(22) +#define MISTA_TDU BIT(23) +#define MISTA_TXBERR BIT(24) + +/* Transmit/Receive Start Demand Register */ +#define ENSTART BIT(0) + +#define ENRXINTR BIT(0) +#define ENCRCE BIT(1) +#define EMRXOV BIT(2) +#define ENPTLE BIT(3) +#define ENRXGD BIT(4) +#define ENALIE BIT(5) +#define ENRP BIT(6) +#define ENMMP BIT(7) +#define ENDFO BIT(8) +#define ENDENI BIT(9) +#define ENRDU BIT(10) +#define ENRXBERR BIT(11) +#define ENCFR BIT(14) +#define ENTXINTR BIT(16) +#define ENTXEMP BIT(17) +#define ENTXCP BIT(18) +#define ENTXDEF BIT(19) +#define ENNCS BIT(20) +#define ENTXABT BIT(21) +#define ENLC BIT(22) +#define ENTDU BIT(23) +#define ENTXBERR BIT(24) + +/* rx and tx owner bit */ +#define RX_OWN_DMA BIT(31) +#define TX_OWN_DMA BIT(31) + +/* tx frame desc controller bit */ +#define MACTXINTEN BIT(2) +#define CRCMODE BIT(1) +#define PADDINGMODE BIT(0) + +/* fftcr controller bit */ +#define RXTHD (0x03 << 0) +#define TXTHD (0x02 << 8) +#define BLENGTH (0x02 << 20) + +/* global setting for driver */ +#define RX_QUEUE_LEN 128 +#define TX_QUEUE_LEN 64 +#define MAX_RBUFF_SZ 0x600 +#define MAX_TBUFF_SZ 0x600 +#define TX_TIMEOUT 50 +#define DELAY 1000 +#define CAM0 0x0 +#define RX_POLL_SIZE 16 + +#ifdef CONFIG_VLAN_8021Q +#define IS_VLAN 1 +#else +#define IS_VLAN 0 +#endif + +#define MAX_PACKET_SIZE (1514 + (IS_VLAN * 4)) +#define MAX_PACKET_SIZE_W_CRC (MAX_PACKET_SIZE + 4) /* 1518 */ + +#define MHZ (1000 * 1000) +#define MII_TIMEOUT 100 + +struct plat_npcm7xx_emc_data { + char *phy_bus_name; + int phy_addr; + unsigned char mac_addr[ETH_ALEN]; +}; + +struct npcm7xx_rxbd { + __le32 sl; + __le32 buffer; + __le32 reserved; + __le32 next; +}; + +struct npcm7xx_txbd { + __le32 mode; /* Ownership bit and some other bits */ + __le32 buffer; /* Transmit Buffer Starting Address */ + __le32 sl; /* Transmit Byte Count and status bits */ + __le32 next; /* Next Tx Descriptor Starting Address */ +}; + +struct npcm7xx_ether { + struct sk_buff *rx_skb[RX_QUEUE_LEN]; + struct sk_buff *tx_skb[TX_QUEUE_LEN]; + spinlock_t lock; /* lock sk */ + struct npcm7xx_rxbd *rdesc; + struct npcm7xx_txbd *tdesc; + dma_addr_t rdesc_phys; + dma_addr_t tdesc_phys; + struct net_device_stats stats; + struct platform_device *pdev; + struct net_device *netdev; + struct resource *res; + unsigned int msg_enable; + struct mii_bus *mii_bus; + struct phy_device *phy_dev; + struct napi_struct napi; + struct ncsi_dev *ncsidev; + bool use_ncsi; + void __iomem *reg; + int rxirq; + int txirq; + unsigned int cur_tx; + unsigned int cur_rx; + unsigned int finish_tx; + unsigned int pending_tx; + __le32 start_tx_ptr; + __le32 start_rx_ptr; + unsigned int rx_berr; + unsigned int rx_err; + unsigned int rdu; + unsigned int rxov; + __le32 camcmr; + unsigned int rx_stuck; + int link; + int speed; + int duplex; + int need_reset; + char *dump_buf; + struct reset_control *reset; + + /* Scratch page to use when rx skb alloc fails */ + void *rx_scratch; + dma_addr_t rx_scratch_dma; + + /* debug counters */ + unsigned int max_waiting_rx; + unsigned int rx_count_pool; + unsigned int count_xmit; + unsigned int rx_int_count; + unsigned int rx_err_count; + unsigned int tx_int_count; + unsigned int tx_tdu; + unsigned int tx_tdu_i; + unsigned int tx_cp_i; + unsigned int count_finish; + +#ifdef CONFIG_DEBUG_FS + struct dentry *dbgfs_dir; + struct dentry *dbgfs_status; + struct dentry *dbgfs_dma_cap; +#endif +}; + +#if defined CONFIG_NPCM7XX_EMC_ETH_DEBUG || defined CONFIG_DEBUG_FS +#define REG_PRINT(reg_name) {t = scnprintf(next, size, "%-10s = %08X\n", \ + #reg_name, readl(ether->reg + (reg_name))); size -= t; next += t; } +#define DUMP_PRINT(f, x...) {t = scnprintf(next, size, f, ## x); size -= t; \ + next += t; } + +static int npcm7xx_info_dump(char *buf, int count, struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + struct npcm7xx_txbd *txbd; + struct npcm7xx_rxbd *rxbd; + unsigned long flags; + unsigned int i, cur, txd_offset, rxd_offset; + char *next = buf; + unsigned int size = count; + int t; + int is_locked = spin_is_locked(ðer->lock); + + if (!is_locked) + spin_lock_irqsave(ðer->lock, flags); + + /* ------basic driver information ---- */ + DUMP_PRINT("NPCM7XX EMC %s driver version: %s\n", netdev->name, + DRV_MODULE_VERSION); + + REG_PRINT(REG_CAMCMR); + REG_PRINT(REG_CAMEN); + REG_PRINT(REG_CAMM_BASE); + REG_PRINT(REG_CAML_BASE); + REG_PRINT(REG_TXDLSA); + REG_PRINT(REG_RXDLSA); + REG_PRINT(REG_MCMDR); + REG_PRINT(REG_MIID); + REG_PRINT(REG_MIIDA); + REG_PRINT(REG_FFTCR); + REG_PRINT(REG_TSDR); + REG_PRINT(REG_RSDR); + REG_PRINT(REG_DMARFC); + REG_PRINT(REG_MIEN); + REG_PRINT(REG_MISTA); + REG_PRINT(REG_MGSTA); + REG_PRINT(REG_MPCNT); + writel(0x7FFF, (ether->reg + REG_MPCNT)); + REG_PRINT(REG_MRPC); + REG_PRINT(REG_MRPCC); + REG_PRINT(REG_MREPC); + REG_PRINT(REG_DMARFS); + REG_PRINT(REG_CTXDSA); + REG_PRINT(REG_CTXBSA); + REG_PRINT(REG_CRXDSA); + REG_PRINT(REG_CRXBSA); + REG_PRINT(REG_RXFSM); + REG_PRINT(REG_TXFSM); + REG_PRINT(REG_FSM0); + REG_PRINT(REG_FSM1); + REG_PRINT(REG_DCR); + REG_PRINT(REG_DMMIR); + REG_PRINT(REG_BISTR); + DUMP_PRINT("\n"); + + DUMP_PRINT("netif_queue %s\n\n", netif_queue_stopped(netdev) ? + "Stopped" : "Running"); + if (ether->rdesc) + DUMP_PRINT("napi is %s\n\n", test_bit(NAPI_STATE_SCHED, + ðer->napi.state) ? + "scheduled" : + "not scheduled"); + + txd_offset = (readl((ether->reg + REG_CTXDSA)) - + readl((ether->reg + REG_TXDLSA))) / + sizeof(struct npcm7xx_txbd); + DUMP_PRINT("TXD offset %6d\n", txd_offset); + DUMP_PRINT("cur_tx %6d\n", ether->cur_tx); + DUMP_PRINT("finish_tx %6d\n", ether->finish_tx); + DUMP_PRINT("pending_tx %6d\n", ether->pending_tx); + /* debug counters */ + DUMP_PRINT("tx_tdu %6d\n", ether->tx_tdu); + ether->tx_tdu = 0; + DUMP_PRINT("tx_tdu_i %6d\n", ether->tx_tdu_i); + ether->tx_tdu_i = 0; + DUMP_PRINT("tx_cp_i %6d\n", ether->tx_cp_i); + ether->tx_cp_i = 0; + DUMP_PRINT("tx_int_count %6d\n", ether->tx_int_count); + ether->tx_int_count = 0; + DUMP_PRINT("count_xmit tx %6d\n", ether->count_xmit); + ether->count_xmit = 0; + DUMP_PRINT("count_finish %6d\n", ether->count_finish); + ether->count_finish = 0; + DUMP_PRINT("\n"); + + rxd_offset = (readl((ether->reg + REG_CRXDSA)) - + readl((ether->reg + REG_RXDLSA))) + / sizeof(struct npcm7xx_txbd); + DUMP_PRINT("RXD offset %6d\n", rxd_offset); + DUMP_PRINT("cur_rx %6d\n", ether->cur_rx); + DUMP_PRINT("rx_err %6d\n", ether->rx_err); + ether->rx_err = 0; + DUMP_PRINT("rx_berr %6d\n", ether->rx_berr); + ether->rx_berr = 0; + DUMP_PRINT("rx_stuck %6d\n", ether->rx_stuck); + ether->rx_stuck = 0; + DUMP_PRINT("rdu %6d\n", ether->rdu); + ether->rdu = 0; + DUMP_PRINT("rxov rx %6d\n", ether->rxov); + ether->rxov = 0; + /* debug counters */ + DUMP_PRINT("rx_int_count %6d\n", ether->rx_int_count); + ether->rx_int_count = 0; + DUMP_PRINT("rx_err_count %6d\n", ether->rx_err_count); + ether->rx_err_count = 0; + DUMP_PRINT("rx_count_pool %6d\n", ether->rx_count_pool); + ether->rx_count_pool = 0; + DUMP_PRINT("max_waiting_rx %5d\n", ether->max_waiting_rx); + ether->max_waiting_rx = 0; + DUMP_PRINT("\n"); + DUMP_PRINT("need_reset %5d\n", ether->need_reset); + + if (ether->tdesc && ether->rdesc) { + cur = ether->finish_tx - 2; + for (i = 0; i < 3; i++) { + cur = (cur + 1) % TX_QUEUE_LEN; + txbd = (ether->tdesc + cur); + DUMP_PRINT("finish %3d txbd mode %08X buffer %08X sl %08X next %08X tx_skb %p\n", + cur, txbd->mode, txbd->buffer, + txbd->sl, txbd->next, ether->tx_skb[cur]); + } + DUMP_PRINT("\n"); + + cur = txd_offset - 2; + for (i = 0; i < 3; i++) { + cur = (cur + 1) % TX_QUEUE_LEN; + txbd = (ether->tdesc + cur); + DUMP_PRINT("txd_of %3d txbd mode %08X buffer %08X sl %08X next %08X\n", + cur, txbd->mode, txbd->buffer, + txbd->sl, txbd->next); + } + DUMP_PRINT("\n"); + + cur = ether->cur_tx - 63; + for (i = 0; i < 64; i++) { + cur = (cur + 1) % TX_QUEUE_LEN; + txbd = (ether->tdesc + cur); + DUMP_PRINT("cur_tx %3d txbd mode %08X buffer %08X sl %08X next %08X\n", + cur, txbd->mode, txbd->buffer, + txbd->sl, txbd->next); + } + DUMP_PRINT("\n"); + + cur = ether->cur_rx - 63; + for (i = 0; i < 64; i++) { + cur = (cur + 1) % RX_QUEUE_LEN; + rxbd = (ether->rdesc + cur); + DUMP_PRINT("cur_rx %3d rxbd sl %08X buffer %08X sl %08X next %08X\n", + cur, rxbd->sl, rxbd->buffer, + rxbd->reserved, rxbd->next); + } + DUMP_PRINT("\n"); + + cur = rxd_offset - 2; + for (i = 0; i < 3; i++) { + cur = (cur + 1) % RX_QUEUE_LEN; + rxbd = (ether->rdesc + cur); + DUMP_PRINT("rxd_of %3d rxbd sl %08X buffer %08X sl %08X next %08X\n", + cur, rxbd->sl, rxbd->buffer, + rxbd->reserved, rxbd->next); + } + DUMP_PRINT("\n"); + } + + if (!is_locked) + spin_unlock_irqrestore(ðer->lock, flags); + + return count - size; +} +#endif + +#ifdef CONFIG_NPCM7XX_EMC_ETH_DEBUG +static void npcm7xx_info_print(struct net_device *netdev) +{ + char *emc_dump_buf; + int count; + struct npcm7xx_ether *ether; + struct platform_device *pdev; + char c; + char *tmp_buf; + const size_t print_size = 5 * PAGE_SIZE; + + ether = netdev_priv(netdev); + pdev = ether->pdev; + + emc_dump_buf = kmalloc(print_size, GFP_KERNEL); + if (!emc_dump_buf) + return; + + tmp_buf = emc_dump_buf; + count = npcm7xx_info_dump(emc_dump_buf, print_size, netdev); + while (count > 512) { + c = tmp_buf[512]; + tmp_buf[512] = 0; + dev_info(&pdev->dev, "%s", tmp_buf); + tmp_buf += 512; + tmp_buf[0] = c; + count -= 512; + + dev_info(&pdev->dev, "%s", tmp_buf); + kfree(emc_dump_buf); +} +#endif + +#ifdef CONFIG_DEBUG_FS +#include + +static int npcm7xx_debug_show(struct seq_file *sf, void *v) +{ + struct net_device *netdev = (struct net_device *)sf->private; + struct npcm7xx_ether *ether = netdev_priv(netdev); + const size_t print_size = 5 * PAGE_SIZE; + + if (!ether->dump_buf) { + ether->dump_buf = kmalloc(print_size, GFP_KERNEL); + if (!ether->dump_buf) + return -1; + npcm7xx_info_dump(ether->dump_buf, print_size, netdev); + } + + seq_printf(sf, "%s", ether->dump_buf); + if (sf->count < sf->size) { + kfree(ether->dump_buf); + ether->dump_buf = NULL; + } + + return 0; +} + +static int npcm7xx_debug_show_open(struct inode *inode, struct file *file) +{ + return single_open(file, npcm7xx_debug_show, inode->i_private); +} + +static const struct file_operations npcm7xx_debug_show_fops = { + .open = npcm7xx_debug_show_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int npcm7xx_debug_reset(struct seq_file *sf, void *v) +{ + struct net_device *netdev = (struct net_device *)sf->private; + struct npcm7xx_ether *ether = netdev_priv(netdev); + unsigned long flags; + + seq_puts(sf, "Ask to reset the module\n"); + spin_lock_irqsave(ðer->lock, flags); + writel(0, (ether->reg + REG_MIEN)); + spin_unlock_irqrestore(ðer->lock, flags); + ether->need_reset = 1; + napi_schedule(ðer->napi); + + return 0; +} + +static int npcm7xx_debug_reset_open(struct inode *inode, struct file *file) +{ + return single_open(file, npcm7xx_debug_reset, inode->i_private); +} + +static const struct file_operations npcm7xx_debug_reset_fops = { + .owner = THIS_MODULE, + .open = npcm7xx_debug_reset_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int npcm7xx_debug_fs(struct npcm7xx_ether *ether) +{ + /* Create debugfs main directory if it doesn't exist yet */ + if (!npcm7xx_fs_dir) { + npcm7xx_fs_dir = debugfs_create_dir(DRV_MODULE_NAME, NULL); + + if (!npcm7xx_fs_dir || IS_ERR(npcm7xx_fs_dir)) { + dev_err(ðer->pdev->dev, + "ERROR %s, debugfs create directory failed\n", + DRV_MODULE_NAME); + return -ENOMEM; + } + } + + /* Create per netdev entries */ + ether->dbgfs_dir = debugfs_create_dir(ether->netdev->name, + npcm7xx_fs_dir); + if (!ether->dbgfs_dir || IS_ERR(ether->dbgfs_dir)) { + dev_err(ðer->pdev->dev, + "ERROR failed to create %s directory\n", + ether->netdev->name); + return -ENOMEM; + } + + /* Entry to report DMA RX/TX rings */ + ether->dbgfs_status = + debugfs_create_file("status", 0444, + ether->dbgfs_dir, ether->netdev, + &npcm7xx_debug_show_fops); + + if (!ether->dbgfs_status || IS_ERR(ether->dbgfs_status)) { + dev_err(ðer->pdev->dev, + "ERROR creating \'status\' debugfs file\n"); + debugfs_remove_recursive(ether->dbgfs_dir); + + return -ENOMEM; + } + + /* Entry to report the DMA HW features */ + ether->dbgfs_dma_cap = debugfs_create_file("do_reset", 0444, + ether->dbgfs_dir, + ether->netdev, + &npcm7xx_debug_reset_fops); + + if (!ether->dbgfs_dma_cap || IS_ERR(ether->dbgfs_dma_cap)) { + dev_err(ðer->pdev->dev, + "ERROR creating stmmac \'do_reset\' debugfs file\n"); + debugfs_remove_recursive(ether->dbgfs_dir); + + return -ENOMEM; + } + + return 0; +} +#endif + +static void npcm7xx_opmode(struct net_device *netdev, int speed, int duplex) +{ + __le32 val; + struct npcm7xx_ether *ether = netdev_priv(netdev); + + val = readl((ether->reg + REG_MCMDR)); + if (speed == 100) + val |= MCMDR_OPMOD; + else + val &= ~MCMDR_OPMOD; + + if (duplex == DUPLEX_FULL) + val |= MCMDR_FDUP; + else + val &= ~MCMDR_FDUP; + + writel(val, (ether->reg + REG_MCMDR)); +} + +static void adjust_link(struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + struct phy_device *phydev = ether->phy_dev; + bool status_change = false; + + if (phydev->link) { + if (ether->speed != phydev->speed || + ether->duplex != phydev->duplex) { + ether->speed = phydev->speed; + ether->duplex = phydev->duplex; + status_change = true; + } + } else { + ether->speed = 0; + ether->duplex = -1; + } + + if (phydev->link != ether->link) { + ether->link = phydev->link; + status_change = true; + } + + if (status_change) + npcm7xx_opmode(netdev, ether->speed, ether->duplex); +} + +static void npcm7xx_write_cam(struct net_device *netdev, + unsigned int x, unsigned char *pval) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + __le32 msw, lsw; + + msw = (pval[0] << 24) | (pval[1] << 16) | (pval[2] << 8) | pval[3]; + + lsw = (pval[4] << 24) | (pval[5] << 16); + + writel(lsw, (ether->reg + REG_CAML_BASE) + x * CAM_ENTRY_SIZE); + writel(msw, (ether->reg + REG_CAMM_BASE) + x * CAM_ENTRY_SIZE); + dev_dbg(ðer->pdev->dev, + "REG_CAML_BASE = 0x%08X REG_CAMM_BASE = 0x%08X", lsw, msw); +} + +static struct sk_buff *get_new_skb(struct net_device *netdev, u32 i) +{ + __le32 buffer; + struct npcm7xx_ether *ether = netdev_priv(netdev); + struct sk_buff *skb = netdev_alloc_skb(netdev, + roundup(MAX_PACKET_SIZE_W_CRC, 4)); + + if (unlikely(!skb)) { + if (net_ratelimit()) + netdev_warn(netdev, "failed to allocate rx skb\n"); + buffer = ether->rx_scratch_dma; + } else { + /* Do not unmark the following skb_reserve() Receive Buffer + * Starting Address must be aligned to 4 bytes and the following + * line if unmarked will make it align to 2 and this likely will + * hult the RX and crash the linux + * skb_reserve(skb, NET_IP_ALIGN); + */ + skb->dev = netdev; + buffer = dma_map_single(&netdev->dev, + skb->data, + roundup(MAX_PACKET_SIZE_W_CRC, 4), + DMA_FROM_DEVICE); + if (unlikely(dma_mapping_error(&netdev->dev, buffer))) { + if (net_ratelimit()) + netdev_err(netdev, "failed to map rx page\n"); + dev_kfree_skb_any(skb); + buffer = ether->rx_scratch_dma; + skb = NULL; + } + } + ether->rx_skb[i] = skb; + ether->rdesc[i].buffer = buffer; + + return skb; +} + +/* This API must call with Tx/Rx stopped */ +static void npcm7xx_free_desc(struct net_device *netdev, + bool free_also_descriptors) +{ + struct sk_buff *skb; + u32 i; + struct npcm7xx_ether *ether = netdev_priv(netdev); + struct platform_device *pdev = ether->pdev; + + for (i = 0; i < TX_QUEUE_LEN; i++) { + skb = ether->tx_skb[i]; + if (skb) { + dma_unmap_single(&netdev->dev, + (dma_addr_t)(ether->tdesc[i].buffer), + skb->len, DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + ether->tx_skb[i] = NULL; + } + } + + for (i = 0; i < RX_QUEUE_LEN; i++) { + skb = ether->rx_skb[i]; + if (skb) { + dma_unmap_single(&netdev->dev, + (dma_addr_t)(ether->rdesc[i].buffer), + roundup(MAX_PACKET_SIZE_W_CRC, 4), + DMA_FROM_DEVICE); + dev_kfree_skb_any(skb); + ether->rx_skb[i] = NULL; + } + } + + if (free_also_descriptors) { + if (ether->tdesc) + dma_free_coherent(&pdev->dev, + sizeof(struct npcm7xx_txbd) * + TX_QUEUE_LEN, + ether->tdesc, ether->tdesc_phys); + ether->tdesc = NULL; + + if (ether->rdesc) + dma_free_coherent(&pdev->dev, + sizeof(struct npcm7xx_rxbd) * + RX_QUEUE_LEN, + ether->rdesc, ether->rdesc_phys); + ether->rdesc = NULL; + + /* Free scratch packet buffer */ + if (ether->rx_scratch) + dma_free_coherent(&pdev->dev, + roundup(MAX_PACKET_SIZE_W_CRC, 4), + ether->rx_scratch, + ether->rx_scratch_dma); + ether->rx_scratch = NULL; + } +} + +static int npcm7xx_init_desc(struct net_device *netdev) +{ + struct npcm7xx_ether *ether; + struct npcm7xx_txbd *tdesc; + struct npcm7xx_rxbd *rdesc; + struct platform_device *pdev; + unsigned int i; + + ether = netdev_priv(netdev); + pdev = ether->pdev; + + if (!ether->tdesc) { + ether->tdesc = (struct npcm7xx_txbd *) + dma_alloc_coherent(&pdev->dev, + sizeof(struct npcm7xx_txbd) * + TX_QUEUE_LEN, + ðer->tdesc_phys, + GFP_KERNEL); + + if (!ether->tdesc) { + dev_err(&pdev->dev, + "Failed to allocate memory for tx desc\n"); + npcm7xx_free_desc(netdev, true); + return -ENOMEM; + } + } + + if (!ether->rdesc) { + ether->rdesc = (struct npcm7xx_rxbd *) + dma_alloc_coherent(&pdev->dev, + sizeof(struct npcm7xx_rxbd) * + RX_QUEUE_LEN, + ðer->rdesc_phys, + GFP_KERNEL); + + if (!ether->rdesc) { + dev_err(&pdev->dev, + "Failed to allocate memory for rx desc\n"); + npcm7xx_free_desc(netdev, true); + return -ENOMEM; + } + } + + for (i = 0; i < TX_QUEUE_LEN; i++) { + unsigned int offset; + + tdesc = (ether->tdesc + i); + + if (i == TX_QUEUE_LEN - 1) + offset = 0; + else + offset = sizeof(struct npcm7xx_txbd) * (i + 1); + + tdesc->next = ether->tdesc_phys + offset; + tdesc->buffer = (__le32)NULL; + tdesc->sl = 0; + tdesc->mode = 0; + } + + ether->start_tx_ptr = ether->tdesc_phys; + + /* Allocate scratch packet buffer */ + if (!ether->rx_scratch) { + ether->rx_scratch = + dma_alloc_coherent(&pdev->dev, + roundup(MAX_PACKET_SIZE_W_CRC, 4), + ðer->rx_scratch_dma, GFP_KERNEL); + if (!ether->rx_scratch) { + dev_err(&pdev->dev, + "Failed to allocate memory for rx_scratch\n"); + npcm7xx_free_desc(netdev, true); + return -ENOMEM; + } + } + + for (i = 0; i < RX_QUEUE_LEN; i++) { + unsigned int offset; + + rdesc = (ether->rdesc + i); + + if (i == RX_QUEUE_LEN - 1) + offset = 0; + else + offset = sizeof(struct npcm7xx_rxbd) * (i + 1); + + rdesc->next = ether->rdesc_phys + offset; + rdesc->sl = RX_OWN_DMA; + + if (!get_new_skb(netdev, i)) { + dev_err(&pdev->dev, "get_new_skb() failed\n"); + npcm7xx_free_desc(netdev, true); + return -ENOMEM; + } + } + + ether->start_rx_ptr = ether->rdesc_phys; + for (i = 0; i < TX_QUEUE_LEN; i++) + ether->tx_skb[i] = NULL; + + return 0; +} + +static void npcm7xx_set_fifo_threshold(struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + __le32 val; + + val = RXTHD | TXTHD | BLENGTH; + writel(val, (ether->reg + REG_FFTCR)); +} + +static void npcm7xx_return_default_idle(struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + __le32 val; + __le32 saved_bits; + + val = readl((ether->reg + REG_MCMDR)); + saved_bits = val & (MCMDR_FDUP | MCMDR_OPMOD); + val |= SWR; + writel(val, (ether->reg + REG_MCMDR)); + + /* During the EMC reset the AHB will read 0 from all registers, + * so in order to see if the reset finished we can't count on + * (ether->reg + REG_MCMDR).SWR to become 0, instead we read another + * register that its reset value is not 0, + * we choose (ether->reg + REG_FFTCR). + */ + do { + val = readl((ether->reg + REG_FFTCR)); + } while (val == 0); + + /* Now we can verify if (ether->reg + REG_MCMDR).SWR became + * 0 (probably it will be 0 on the first read). + */ + do { + val = readl((ether->reg + REG_MCMDR)); + } while (val & SWR); + + /* restore values */ + writel(saved_bits, (ether->reg + REG_MCMDR)); +} + +static void npcm7xx_enable_mac_interrupt(struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + __le32 val; + + val = ENRXINTR | /* Start of RX interrupts */ + ENCRCE | + EMRXOV | + (ENPTLE * !IS_VLAN) | /* If we don't support VLAN we want interrupt on long packets */ + ENRXGD | + ENALIE | + ENRP | + ENMMP | + ENDFO | + /* ENDENI | */ /* We don't need interrupt on DMA Early Notification */ + ENRDU | /* We don't need interrupt on Receive Descriptor Unavailable Interrupt */ + ENRXBERR | + /* ENCFR | */ + ENTXINTR | /* Start of TX interrupts */ + ENTXEMP | + ENTXCP | + ENTXDEF | + ENNCS | + ENTXABT | + ENLC | + /* ENTDU | */ /* We don't need interrupt on Transmit Descriptor Unavailable at start of operation */ + ENTXBERR; + writel(val, (ether->reg + REG_MIEN)); +} + +static void npcm7xx_get_and_clear_int(struct net_device *netdev, + __le32 *val, __le32 mask) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + + *val = readl((ether->reg + REG_MISTA)) & mask; + writel(*val, (ether->reg + REG_MISTA)); +} + +static void npcm7xx_set_global_maccmd(struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + __le32 val; + + val = readl((ether->reg + REG_MCMDR)); + + val |= MCMDR_SPCRC | MCMDR_ENMDC | MCMDR_ACP | MCMDR_NDEF; + if (IS_VLAN) { + /* we set ALP accept long packets since VLAN packets + * are 4 bytes longer than 1518 + */ + val |= MCMDR_ALP; + /* limit receive length to 1522 bytes due to VLAN */ + writel(MAX_PACKET_SIZE_W_CRC, (ether->reg + REG_DMARFC)); + } + writel(val, (ether->reg + REG_MCMDR)); +} + +static void npcm7xx_enable_cam(struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + __le32 val; + + npcm7xx_write_cam(netdev, CAM0, netdev->dev_addr); + + val = readl((ether->reg + REG_CAMEN)); + val |= CAM0EN; + writel(val, (ether->reg + REG_CAMEN)); +} + +static void npcm7xx_set_curdest(struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + + writel(ether->start_rx_ptr, (ether->reg + REG_RXDLSA)); + writel(ether->start_tx_ptr, (ether->reg + REG_TXDLSA)); +} + +static void npcm7xx_ether_set_rx_mode(struct net_device *netdev) +{ + struct npcm7xx_ether *ether; + __le32 rx_mode; + + ether = netdev_priv(netdev); + + dev_dbg(ðer->pdev->dev, "%s CAMCMR_AUP\n", + (netdev->flags & IFF_PROMISC) ? "Set" : "Clear"); + if (netdev->flags & IFF_PROMISC) + rx_mode = CAMCMR_AUP | CAMCMR_AMP | CAMCMR_ABP | CAMCMR_ECMP; + else if ((netdev->flags & IFF_ALLMULTI) || !netdev_mc_empty(netdev)) + rx_mode = CAMCMR_AMP | CAMCMR_ABP | CAMCMR_ECMP; + else + rx_mode = CAMCMR_ECMP | CAMCMR_ABP; + writel(rx_mode, (ether->reg + REG_CAMCMR)); + ether->camcmr = rx_mode; +} + +static void npcm7xx_reset_mac(struct net_device *netdev, int need_free) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + + netif_tx_lock(netdev); + + /* disable RX and TX */ + writel(readl((ether->reg + REG_MCMDR)) & ~(MCMDR_TXON | MCMDR_RXON), + (ether->reg + REG_MCMDR)); + + npcm7xx_return_default_idle(netdev); + npcm7xx_set_fifo_threshold(netdev); + + if (need_free) + npcm7xx_free_desc(netdev, false); + + npcm7xx_init_desc(netdev); + + ether->cur_tx = 0x0; + ether->finish_tx = 0x0; + ether->pending_tx = 0x0; + ether->cur_rx = 0x0; + ether->tx_tdu = 0; + ether->tx_tdu_i = 0; + ether->tx_cp_i = 0; + + npcm7xx_set_curdest(netdev); + npcm7xx_enable_cam(netdev); + npcm7xx_ether_set_rx_mode(netdev); + npcm7xx_enable_mac_interrupt(netdev); + npcm7xx_set_global_maccmd(netdev); + + /* enable RX and TX */ + writel(readl((ether->reg + REG_MCMDR)) | MCMDR_TXON | MCMDR_RXON, + (ether->reg + REG_MCMDR)); + + /* trigger RX */ + writel(ENSTART, (ether->reg + REG_RSDR)); + + ether->need_reset = 0; + + netif_wake_queue(netdev); + netif_tx_unlock(netdev); +} + +static int npcm7xx_mdio_write(struct mii_bus *bus, int phy_id, int regnum, + u16 value) +{ + struct npcm7xx_ether *ether = bus->priv; + unsigned long timeout = jiffies + msecs_to_jiffies(MII_TIMEOUT * 100); + + writel(value, (ether->reg + REG_MIID)); + writel((phy_id << 0x08) | regnum | PHYBUSY | PHYWR, + (ether->reg + REG_MIIDA)); + + /* Wait for completion */ + while (readl((ether->reg + REG_MIIDA)) & PHYBUSY) { + if (time_after(jiffies, timeout)) { + dev_dbg(ðer->pdev->dev, + "mdio read timed out\n ether->reg = 0x%x phy_id=0x%x REG_MIIDA=0x%x\n", + (unsigned int)ether->reg, phy_id, + readl((ether->reg + REG_MIIDA))); + return -ETIMEDOUT; + } + cpu_relax(); + } + + return 0; +} + +static int npcm7xx_mdio_read(struct mii_bus *bus, int phy_id, int regnum) +{ + struct npcm7xx_ether *ether = bus->priv; + unsigned long timeout = jiffies + msecs_to_jiffies(MII_TIMEOUT * 100); + + writel((phy_id << 0x08) | regnum | PHYBUSY, (ether->reg + REG_MIIDA)); + + /* Wait for completion */ + while (readl((ether->reg + REG_MIIDA)) & PHYBUSY) { + if (time_after(jiffies, timeout)) { + dev_dbg(ðer->pdev->dev, + "mdio read timed out\n ether->reg = 0x%x phy_id=0x%x REG_MIIDA=0x%x\n", + (unsigned int)ether->reg, phy_id + , readl((ether->reg + REG_MIIDA))); + return -ETIMEDOUT; + } + cpu_relax(); + } + + return readl((ether->reg + REG_MIID)); +} + +static int npcm7xx_mdio_reset(struct mii_bus *bus) +{ + /* reset EMAC engine?? */ + return 0; +} + +static int npcm7xx_set_mac_address(struct net_device *netdev, void *addr) +{ + struct sockaddr *address = addr; + + if (!is_valid_ether_addr((u8 *)address->sa_data)) + return -EADDRNOTAVAIL; + + memcpy(netdev->dev_addr, address->sa_data, netdev->addr_len); + npcm7xx_write_cam(netdev, CAM0, netdev->dev_addr); + + return 0; +} + +static int npcm7xx_ether_close(struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + + npcm7xx_return_default_idle(netdev); + + if (ether->phy_dev) + phy_stop(ether->phy_dev); + else if (ether->use_ncsi) + ncsi_stop_dev(ether->ncsidev); + + msleep(20); + + free_irq(ether->txirq, netdev); + free_irq(ether->rxirq, netdev); + + netif_stop_queue(netdev); + napi_disable(ðer->napi); + + npcm7xx_free_desc(netdev, true); + + kfree(ether->dump_buf); + ether->dump_buf = NULL; + + return 0; +} + +static struct net_device_stats *npcm7xx_ether_stats(struct net_device *netdev) +{ + struct npcm7xx_ether *ether; + + ether = netdev_priv(netdev); + return ðer->stats; +} + +static int npcm7xx_clean_tx(struct net_device *netdev, bool from_xmit) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + struct npcm7xx_txbd *txbd; + struct sk_buff *s; + dma_addr_t cur_entry, entry; + __le32 sl; + + if (ether->pending_tx == 0) + return (0); + + cur_entry = readl((ether->reg + REG_CTXDSA)); + + /* Release old used buffers */ + entry = ether->tdesc_phys + sizeof(struct npcm7xx_txbd) * + (ether->finish_tx); + + while (entry != cur_entry) { + txbd = (ether->tdesc + ether->finish_tx); + s = ether->tx_skb[ether->finish_tx]; + if (!s) + break; + + ether->count_finish++; + + dma_unmap_single(&netdev->dev, txbd->buffer, s->len, + DMA_TO_DEVICE); + consume_skb(s); + ether->tx_skb[ether->finish_tx] = NULL; + + if (++ether->finish_tx >= TX_QUEUE_LEN) + ether->finish_tx = 0; + ether->pending_tx--; + + sl = txbd->sl; + if (sl & TXDS_TXCP) { + ether->stats.tx_packets++; + ether->stats.tx_bytes += (sl & 0xFFFF); + } else { + ether->stats.tx_errors++; + } + + entry = ether->tdesc_phys + sizeof(struct npcm7xx_txbd) * + (ether->finish_tx); + } + + if (!from_xmit && unlikely(netif_queue_stopped(netdev) && + (TX_QUEUE_LEN - ether->pending_tx) > 1)) { + netif_tx_lock(netdev); + if (netif_queue_stopped(netdev) && + (TX_QUEUE_LEN - ether->pending_tx) > 1) { + netif_wake_queue(netdev); + } + netif_tx_unlock(netdev); + } + + return(0); +} + +static int npcm7xx_ether_start_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + struct npcm7xx_txbd *txbd; + unsigned long flags; + + ether->count_xmit++; + + /* Insert new buffer */ + txbd = (ether->tdesc + ether->cur_tx); + txbd->buffer = dma_map_single(&netdev->dev, skb->data, skb->len, + DMA_TO_DEVICE); + ether->tx_skb[ether->cur_tx] = skb; + if (skb->len > MAX_PACKET_SIZE) + dev_err(ðer->pdev->dev, + "skb->len (= %d) > MAX_PACKET_SIZE (= %d)\n", + skb->len, MAX_PACKET_SIZE); + + txbd->sl = skb->len > MAX_PACKET_SIZE ? MAX_PACKET_SIZE : skb->len; + dma_wmb(); + + txbd->mode = TX_OWN_DMA | PADDINGMODE | CRCMODE; + + /* make sure that data is in memory before we trigger TX */ + wmb(); + + /* trigger TX */ + writel(ENSTART, (ether->reg + REG_TSDR)); + + if (++ether->cur_tx >= TX_QUEUE_LEN) + ether->cur_tx = 0; + + spin_lock_irqsave(ðer->lock, flags); + ether->pending_tx++; + + /* sometimes we miss the tx interrupt due to HW issue, so NAPI will not + * clean the pending tx, so we clean it also here + */ + npcm7xx_clean_tx(netdev, true); + + if (ether->pending_tx >= TX_QUEUE_LEN - 1) { + __le32 reg_mien; + unsigned int index_to_wake = ether->cur_tx + + ((TX_QUEUE_LEN * 3) / 4); + + if (index_to_wake >= TX_QUEUE_LEN) + index_to_wake -= TX_QUEUE_LEN; + + txbd = (ether->tdesc + index_to_wake); + txbd->mode = TX_OWN_DMA | PADDINGMODE | CRCMODE | MACTXINTEN; + + /* make sure that data is in memory before we trigger TX */ + wmb(); + + /* Clear TDU interrupt */ + writel(MISTA_TDU, (ether->reg + REG_MISTA)); + + /* due to HW issue somtimes, we miss the TX interrupt we just + * set (MACTXINTEN), so we also set TDU for Transmit + * Descriptor Unavailable interrupt + */ + reg_mien = readl((ether->reg + REG_MIEN)); + if (reg_mien != 0) + /* Enable TDU interrupt */ + writel(reg_mien | ENTDU, (ether->reg + REG_MIEN)); + + ether->tx_tdu++; + netif_stop_queue(netdev); + } + + spin_unlock_irqrestore(ðer->lock, flags); + + return 0; +} + +static irqreturn_t npcm7xx_tx_interrupt(int irq, void *dev_id) +{ + struct npcm7xx_ether *ether; + struct platform_device *pdev; + struct net_device *netdev; + __le32 status; + unsigned long flags; + + netdev = dev_id; + ether = netdev_priv(netdev); + pdev = ether->pdev; + + npcm7xx_get_and_clear_int(netdev, &status, 0xFFFF0000); + + ether->tx_int_count++; + + if (status & MISTA_EXDEF) + dev_err(&pdev->dev, "emc defer exceed interrupt status=0x%08X\n" + , status); + else if (status & MISTA_TXBERR) { + dev_err(&pdev->dev, "emc bus error interrupt status=0x%08X\n", + status); +#ifdef CONFIG_NPCM7XX_EMC_ETH_DEBUG + npcm7xx_info_print(netdev); +#endif + spin_lock_irqsave(ðer->lock, flags); + writel(0, (ether->reg + REG_MIEN)); /* disable any interrupt */ + spin_unlock_irqrestore(ðer->lock, flags); + ether->need_reset = 1; + } else if (status & ~(MISTA_TXINTR | MISTA_TXCP | MISTA_TDU)) + dev_err(&pdev->dev, "emc other error interrupt status=0x%08X\n", + status); + + /* if we got MISTA_TXCP | MISTA_TDU remove those interrupt and call napi */ + if (status & (MISTA_TXCP | MISTA_TDU) & + readl((ether->reg + REG_MIEN))) { + __le32 reg_mien; + + spin_lock_irqsave(ðer->lock, flags); + reg_mien = readl((ether->reg + REG_MIEN)); + if (reg_mien & ENTDU) + /* Disable TDU interrupt */ + writel(reg_mien & (~ENTDU), (ether->reg + REG_MIEN)); + + spin_unlock_irqrestore(ðer->lock, flags); + + if (status & MISTA_TXCP) + ether->tx_cp_i++; + if (status & MISTA_TDU) + ether->tx_tdu_i++; + } else { + dev_dbg(&pdev->dev, "status=0x%08X\n", status); + } + + napi_schedule(ðer->napi); + + return IRQ_HANDLED; +} + +static irqreturn_t npcm7xx_rx_interrupt(int irq, void *dev_id) +{ + struct net_device *netdev = (struct net_device *)dev_id; + struct npcm7xx_ether *ether = netdev_priv(netdev); + struct platform_device *pdev = ether->pdev; + __le32 status; + unsigned long flags; + unsigned int any_err = 0; + __le32 rxfsm; + + npcm7xx_get_and_clear_int(netdev, &status, 0xFFFF); + ether->rx_int_count++; + + if (unlikely(status & MISTA_RXBERR)) { + ether->rx_berr++; + dev_err(&pdev->dev, "emc rx bus error status=0x%08X\n", status); +#ifdef CONFIG_NPCM7XX_EMC_ETH_DEBUG + npcm7xx_info_print(netdev); +#endif + spin_lock_irqsave(ðer->lock, flags); + writel(0, (ether->reg + REG_MIEN)); /* disable any interrupt */ + spin_unlock_irqrestore(ðer->lock, flags); + ether->need_reset = 1; + napi_schedule(ðer->napi); + return IRQ_HANDLED; + } + + if (unlikely(status & (MISTA_RXOV | MISTA_RDU))) { + /* filter out all received packets until we have + * enough available buffer descriptors + */ + writel(0, (ether->reg + REG_CAMCMR)); + any_err = 1; + if (status & (MISTA_RXOV)) + ether->rxov++; + if (status & (MISTA_RDU)) + ether->rdu++; + + /* workaround Errata 1.36: EMC Hangs on receiving 253-256 + * byte packet + */ + rxfsm = readl((ether->reg + REG_RXFSM)); + + if ((rxfsm & 0xFFFFF000) == 0x08044000) { + int i; + + for (i = 0; i < 32; i++) { + rxfsm = readl((ether->reg + REG_RXFSM)); + if ((rxfsm & 0xFFFFF000) != 0x08044000) + break; + } + if (i == 32) { + ether->rx_stuck++; + spin_lock_irqsave(ðer->lock, flags); +#ifdef CONFIG_NPCM7XX_EMC_ETH_DEBUG + npcm7xx_info_print(netdev); +#endif + writel(0, (ether->reg + REG_MIEN)); + spin_unlock_irqrestore(ðer->lock, flags); + ether->need_reset = 1; + napi_schedule(ðer->napi); + dev_err(&pdev->dev, + "stuck on REG_RXFSM = 0x%08X status=%08X doing reset!\n", + rxfsm, status); + return IRQ_HANDLED; + } + } + } + + /* echo MISTA status on unexpected flags although we don't do anithing + * with them + */ + if (unlikely(status & + (/* MISTA_RXINTR | */ /* Receive - all RX interrupt set this */ + MISTA_CRCE | /* CRC Error */ + /* MISTA_RXOV | */ /* Receive FIFO Overflow - we alread handled it */ + (!IS_VLAN * MISTA_PTLE) | /* Packet Too Long is needed if VLAN is not supported */ + /* MISTA_RXGD | */ /* Receive Good - this is the common good case */ + MISTA_ALIE | /* Alignment Error */ + MISTA_RP | /* Runt Packet */ + MISTA_MMP | /* More Missed Packet */ + MISTA_DFOI | /* Maximum Frame Length */ + /* MISTA_DENI | */ /* DMA Early Notification - every packet get this */ + /* MISTA_RDU | */ /* Receive Descriptor Unavailable */ + /* MISTA_RXBERR | */ /* Receive Bus Error Interrupt - we alread handled it */ + /* MISTA_CFR | */ /* Control Frame Receive - not an error */ + 0))) { + dev_dbg(&pdev->dev, "emc rx MISTA status=0x%08X\n", status); + any_err = 1; + ether->rx_err++; + } + + if (!any_err && ((status & MISTA_RXGD) == 0)) + dev_err(&pdev->dev, "emc rx MISTA status=0x%08X\n", status); + + spin_lock_irqsave(ðer->lock, flags); + writel(readl((ether->reg + REG_MIEN)) & ~ENRXGD, + (ether->reg + REG_MIEN)); + spin_unlock_irqrestore(ðer->lock, flags); + napi_schedule(ðer->napi); + + return IRQ_HANDLED; +} + +static int npcm7xx_poll(struct napi_struct *napi, int budget) +{ + struct npcm7xx_ether *ether = + container_of(napi, struct npcm7xx_ether, napi); + struct npcm7xx_rxbd *rxbd; + struct net_device *netdev = ether->netdev; + struct platform_device *pdev = ether->pdev; + struct sk_buff *s; + unsigned int length; + __le32 status; + unsigned long flags; + int rx_cnt = 0; + int complete = 0; + unsigned int rx_offset = (readl((ether->reg + REG_CRXDSA)) - + ether->start_rx_ptr) / + sizeof(struct npcm7xx_txbd); + unsigned int local_count = (rx_offset >= ether->cur_rx) ? + rx_offset - ether->cur_rx : rx_offset + + RX_QUEUE_LEN - ether->cur_rx; + + if (local_count > ether->max_waiting_rx) + ether->max_waiting_rx = local_count; + + if (local_count > (4 * RX_POLL_SIZE)) + /* we are porbably in a storm of short packets and we don't + * want to get into RDU since short packets in RDU cause + * many RXOV which may cause EMC halt, so we filter out all + * coming packets + */ + writel(0, (ether->reg + REG_CAMCMR)); + + if (local_count <= budget) + /* we can restore accepting of packets */ + writel(ether->camcmr, (ether->reg + REG_CAMCMR)); + + spin_lock_irqsave(ðer->lock, flags); + npcm7xx_clean_tx(netdev, false); + spin_unlock_irqrestore(ðer->lock, flags); + + rxbd = (ether->rdesc + ether->cur_rx); + + while (rx_cnt < budget) { + status = rxbd->sl; + if ((status & RX_OWN_DMA) == RX_OWN_DMA) { + complete = 1; + break; + } + /* for debug puposes we save the previous value */ + rxbd->reserved = status; + s = ether->rx_skb[ether->cur_rx]; + length = status & 0xFFFF; + + /* If VLAN is not supporte RXDS_PTLE (packet too long) is also + * an error + */ + if (likely((status & (RXDS_RXGD | RXDS_CRCE | RXDS_ALIE | + RXDS_RP | (IS_VLAN ? 0 : RXDS_PTLE))) == + RXDS_RXGD) && likely(length <= MAX_PACKET_SIZE)) { + dma_unmap_single(&netdev->dev, (dma_addr_t)rxbd->buffer, + roundup(MAX_PACKET_SIZE_W_CRC, 4), + DMA_FROM_DEVICE); + + skb_put(s, length); + s->protocol = eth_type_trans(s, netdev); + netif_receive_skb(s); + ether->stats.rx_packets++; + ether->stats.rx_bytes += length; + rx_cnt++; + ether->rx_count_pool++; + + /* now we allocate new skb instead if the used one. */ + if (!get_new_skb(netdev, ether->cur_rx)) + ether->stats.rx_dropped++; + } else { + ether->rx_err_count++; + ether->stats.rx_errors++; + dev_dbg(&pdev->dev, "rx_errors = %lu status = 0x%08X\n", + ether->stats.rx_errors, status); + + if (status & RXDS_RP) { + ether->stats.rx_length_errors++; + dev_dbg(&pdev->dev, "rx_length_errors = %lu\n", + ether->stats.rx_length_errors); + } else if (status & RXDS_CRCE) { + ether->stats.rx_crc_errors++; + dev_dbg(&pdev->dev, "rx_crc_errors = %lu\n", + ether->stats.rx_crc_errors); + } else if (status & RXDS_ALIE) { + ether->stats.rx_frame_errors++; + dev_dbg(&pdev->dev, "rx_frame_errors = %lu\n", + ether->stats.rx_frame_errors); + } else if (((!IS_VLAN) && (status & RXDS_PTLE)) || + length > MAX_PACKET_SIZE) { + ether->stats.rx_length_errors++; + dev_dbg(&pdev->dev, "rx_length_errors = %lu\n", + ether->stats.rx_length_errors); + } + } + + /* make sure other values are set before we set owner */ + wmb(); + + rxbd->sl = RX_OWN_DMA; + /* make sure that data is in memory before we trigger RX */ + wmb(); + + if (++ether->cur_rx >= RX_QUEUE_LEN) + ether->cur_rx = 0; + + rxbd = (ether->rdesc + ether->cur_rx); + } + + if (complete) { + napi_complete(napi); + + if (ether->need_reset) { + dev_dbg(&pdev->dev, "Reset\n"); + npcm7xx_reset_mac(netdev, 1); + } + + spin_lock_irqsave(ðer->lock, flags); + writel(readl((ether->reg + REG_MIEN)) | ENRXGD, (ether->reg + + REG_MIEN)); + spin_unlock_irqrestore(ðer->lock, flags); + } else { + rx_offset = (readl((ether->reg + REG_CRXDSA)) - + ether->start_rx_ptr) / sizeof(struct npcm7xx_txbd); + local_count = (rx_offset >= ether->cur_rx) ? rx_offset - + ether->cur_rx : rx_offset + RX_QUEUE_LEN - + ether->cur_rx; + + if (local_count > ether->max_waiting_rx) + ether->max_waiting_rx = local_count; + + if (local_count > (3 * RX_POLL_SIZE)) + /* we are porbably in a storm of short packets and + * we don't want to get into RDU since short packets in + * RDU cause many RXOV which may cause + * EMC halt, so we filter out all coming packets + */ + writel(0, (ether->reg + REG_CAMCMR)); + if (local_count <= RX_POLL_SIZE) + /* we can restore accepting of packets */ + writel(ether->camcmr, (ether->reg + REG_CAMCMR)); + } + + /* trigger RX */ + writel(ENSTART, (ether->reg + REG_RSDR)); + return rx_cnt; +} + +static int npcm7xx_ether_open(struct net_device *netdev) +{ + struct npcm7xx_ether *ether; + struct platform_device *pdev; + + ether = netdev_priv(netdev); + pdev = ether->pdev; + + if (ether->use_ncsi) { + ether->speed = 100; + ether->duplex = DUPLEX_FULL; + npcm7xx_opmode(netdev, 100, DUPLEX_FULL); + } + npcm7xx_reset_mac(netdev, 0); + + if (request_irq(ether->txirq, npcm7xx_tx_interrupt, 0x0, pdev->name, + netdev)) { + dev_err(&pdev->dev, "register irq tx failed\n"); + npcm7xx_ether_close(netdev); + return -EAGAIN; + } + + if (request_irq(ether->rxirq, npcm7xx_rx_interrupt, 0x0, pdev->name, + netdev)) { + dev_err(&pdev->dev, "register irq rx failed\n"); + npcm7xx_ether_close(netdev); + return -EAGAIN; + } + + if (ether->phy_dev) + phy_start(ether->phy_dev); + else if (ether->use_ncsi) + netif_carrier_on(netdev); + + netif_start_queue(netdev); + napi_enable(ðer->napi); + + /* trigger RX */ + writel(ENSTART, (ether->reg + REG_RSDR)); + + /* Start the NCSI device */ + if (ether->use_ncsi) { + int err = ncsi_start_dev(ether->ncsidev); + + if (err) { + npcm7xx_ether_close(netdev); + return err; + } + } + + dev_info(&pdev->dev, "%s is OPENED\n", netdev->name); + + return 0; +} + +static int npcm7xx_ether_ioctl(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + struct phy_device *phydev = ether->phy_dev; + + if (!netif_running(netdev)) + return -EINVAL; + + if (!phydev) + return -ENODEV; + + return phy_mii_ioctl(phydev, ifr, cmd); +} + +static void npcm7xx_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *info) +{ + strlcpy(info->driver, DRV_MODULE_NAME, sizeof(info->driver)); + strlcpy(info->version, DRV_MODULE_VERSION, sizeof(info->version)); + strlcpy(info->fw_version, "N/A", sizeof(info->fw_version)); + strlcpy(info->bus_info, "N/A", sizeof(info->bus_info)); +} + +static int npcm7xx_get_settings(struct net_device *netdev, + struct ethtool_link_ksettings *cmd) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + struct phy_device *phydev = ether->phy_dev; + + if (!phydev) + return -ENODEV; + + dev_info(ðer->pdev->dev, "\n\nnpcm7xx_get_settings\n"); + phy_ethtool_ksettings_get(phydev, cmd); + + return 0; +} + +static int npcm7xx_set_settings(struct net_device *netdev, + const struct ethtool_link_ksettings *cmd) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + struct phy_device *phydev = ether->phy_dev; + int ret; + + if (!phydev) + return -ENODEV; + + dev_info(ðer->pdev->dev, "\n\nnpcm7xx_set_settings\n"); + ret = phy_ethtool_ksettings_set(phydev, cmd); + + return ret; +} + +static u32 npcm7xx_get_msglevel(struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + + return ether->msg_enable; +} + +static void npcm7xx_set_msglevel(struct net_device *netdev, u32 level) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + + ether->msg_enable = level; +} + +static const struct ethtool_ops npcm7xx_ether_ethtool_ops = { + .get_link_ksettings = npcm7xx_get_settings, + .set_link_ksettings = npcm7xx_set_settings, + .get_drvinfo = npcm7xx_get_drvinfo, + .get_msglevel = npcm7xx_get_msglevel, + .set_msglevel = npcm7xx_set_msglevel, + .get_link = ethtool_op_get_link, +}; + +static const struct net_device_ops npcm7xx_ether_netdev_ops = { + .ndo_open = npcm7xx_ether_open, + .ndo_stop = npcm7xx_ether_close, + .ndo_start_xmit = npcm7xx_ether_start_xmit, + .ndo_get_stats = npcm7xx_ether_stats, + .ndo_set_rx_mode = npcm7xx_ether_set_rx_mode, + .ndo_set_mac_address = npcm7xx_set_mac_address, + .ndo_do_ioctl = npcm7xx_ether_ioctl, + .ndo_validate_addr = eth_validate_addr, + .ndo_change_mtu = eth_change_mtu, +}; + +static void get_mac_address(struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + struct platform_device *pdev = ether->pdev; + struct device_node *np = ether->pdev->dev.of_node; + const u8 *mac_address = NULL; + + mac_address = of_get_mac_address(np); + + if (mac_address != 0) + ether_addr_copy(netdev->dev_addr, mac_address); + + if (is_valid_ether_addr(netdev->dev_addr)) { + dev_info(&pdev->dev, "%s: device MAC address : %pM\n", + pdev->name, netdev->dev_addr); + } else { + eth_hw_addr_random(netdev); + dev_info(&pdev->dev, + "%s: device MAC address (random generator) %pM\n", + netdev->name, netdev->dev_addr); + } +} + +static int npcm7xx_mii_setup(struct net_device *netdev) +{ + struct npcm7xx_ether *ether = netdev_priv(netdev); + struct platform_device *pdev; + struct phy_device *phydev = NULL; + int i, err = 0; + + pdev = ether->pdev; + + ether->mii_bus = mdiobus_alloc(); + if (!ether->mii_bus) { + err = -ENOMEM; + dev_err(&pdev->dev, "mdiobus_alloc() failed\n"); + goto out0; + } + + ether->mii_bus->name = "npcm7xx_rmii"; + ether->mii_bus->read = &npcm7xx_mdio_read; + ether->mii_bus->write = &npcm7xx_mdio_write; + ether->mii_bus->reset = &npcm7xx_mdio_reset; + snprintf(ether->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x", + ether->pdev->name, ether->pdev->id); + dev_dbg(&pdev->dev, "%s ether->mii_bus->id=%s\n", __func__, + ether->mii_bus->id); + ether->mii_bus->priv = ether; + ether->mii_bus->parent = ðer->pdev->dev; + + for (i = 0; i < PHY_MAX_ADDR; i++) + ether->mii_bus->irq[i] = PHY_POLL; + + platform_set_drvdata(ether->pdev, ether->mii_bus); + + /* Enable MDIO Clock */ + writel(readl((ether->reg + REG_MCMDR)) | MCMDR_ENMDC, + (ether->reg + REG_MCMDR)); + + if (mdiobus_register(ether->mii_bus)) { + dev_err(&pdev->dev, "mdiobus_register() failed\n"); + goto out2; + } + + phydev = phy_find_first(ether->mii_bus); + if (!phydev) { + dev_err(&pdev->dev, "phy_find_first() failed\n"); + goto out3; + } + + dev_info(&pdev->dev, " name = %s ETH-Phy-Id = 0x%x\n", + phydev_name(phydev), phydev->phy_id); + + phydev = phy_connect(netdev, phydev_name(phydev), + &adjust_link, + PHY_INTERFACE_MODE_RMII); + + dev_info(&pdev->dev, " ETH-Phy-Id = 0x%x name = %s\n", + phydev->phy_id, phydev->drv->name); + + if (IS_ERR(phydev)) { + err = PTR_ERR(phydev); + dev_err(&pdev->dev, "phy_connect() failed - %d\n", err); + goto out3; + } + + phydev->supported &= PHY_BASIC_FEATURES; + phydev->advertising = phydev->supported; + ether->phy_dev = phydev; + + return 0; + +out3: + mdiobus_unregister(ether->mii_bus); +out2: + kfree(ether->mii_bus->irq); + mdiobus_free(ether->mii_bus); +out0: + + return err; +} + +static const struct of_device_id emc_dt_id[] = { + { .compatible = "nuvoton,npcm750-emc", }, + {}, +}; +MODULE_DEVICE_TABLE(of, emc_dt_id); + +static void npcm7xx_ncsi_handler(struct ncsi_dev *nd) +{ + if (unlikely(nd->state != ncsi_dev_state_functional)) + return; + + netdev_info(nd->dev, "NCSI interface %s\n", + nd->link_up ? "up" : "down"); +} + +static int npcm7xx_ether_probe(struct platform_device *pdev) +{ + struct npcm7xx_ether *ether; + struct net_device *netdev; + int error; + + struct clk *emc_clk = NULL; + struct device_node *np = pdev->dev.of_node; + + pdev->id = of_alias_get_id(np, "ethernet"); + if (pdev->id < 0) + pdev->id = 0; + + emc_clk = devm_clk_get(&pdev->dev, NULL); + + if (IS_ERR(emc_clk)) + return PTR_ERR(emc_clk); + + /* Enable Clock */ + clk_prepare_enable(emc_clk); + + error = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (error) + return -ENODEV; + + netdev = alloc_etherdev(sizeof(struct npcm7xx_ether)); + if (!netdev) + return -ENOMEM; + + ether = netdev_priv(netdev); + + ether->reset = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(ether->reset)) + return PTR_ERR(ether->reset); + + /* Reset EMC module */ + reset_control_assert(ether->reset); + udelay(5); + reset_control_deassert(ether->reset); + + ether->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!ether->res) { + dev_err(&pdev->dev, "failed to get I/O memory\n"); + error = -ENXIO; + goto failed_free; + } + + if (!request_mem_region(ether->res->start, + resource_size(ether->res), pdev->name)) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + error = -EBUSY; + goto failed_free; + } + + ether->reg = ioremap(ether->res->start, resource_size(ether->res)); + dev_dbg(&pdev->dev, "%s ether->reg = 0x%x\n", __func__, + (unsigned int)ether->reg); + + if (!ether->reg) { + dev_err(&pdev->dev, "failed to remap I/O memory\n"); + error = -ENXIO; + goto failed_free_mem; + } + + ether->txirq = platform_get_irq(pdev, 0); + if (ether->txirq < 0) { + dev_err(&pdev->dev, "failed to get ether tx irq\n"); + error = -ENXIO; + goto failed_free_io; + } + + ether->rxirq = platform_get_irq(pdev, 1); + if (ether->rxirq < 0) { + dev_err(&pdev->dev, "failed to get ether rx irq\n"); + error = -ENXIO; + goto failed_free_io; + } + + SET_NETDEV_DEV(netdev, &pdev->dev); + platform_set_drvdata(pdev, netdev); + ether->netdev = netdev; + + ether->pdev = pdev; + ether->msg_enable = NETIF_MSG_LINK; + + netdev->netdev_ops = &npcm7xx_ether_netdev_ops; + netdev->ethtool_ops = &npcm7xx_ether_ethtool_ops; + + netdev->tx_queue_len = TX_QUEUE_LEN; + netdev->dma = 0x0; + netdev->watchdog_timeo = TX_TIMEOUT; + + get_mac_address(netdev); + + ether->speed = 100; + ether->duplex = DUPLEX_FULL; + + spin_lock_init(ðer->lock); + + netif_napi_add(netdev, ðer->napi, npcm7xx_poll, RX_POLL_SIZE); + + if (pdev->dev.of_node && + of_get_property(pdev->dev.of_node, "use-ncsi", NULL)) { + if (!IS_ENABLED(CONFIG_NET_NCSI)) { + dev_err(&pdev->dev, "CONFIG_NET_NCSI not enabled\n"); + error = -ENODEV; + goto failed_free_napi; + } + dev_info(&pdev->dev, "Using NCSI interface\n"); + ether->use_ncsi = true; + ether->ncsidev = ncsi_register_dev(netdev, + npcm7xx_ncsi_handler); + if (!ether->ncsidev) { + error = -ENODEV; + goto failed_free_napi; + } + } else { + ether->use_ncsi = false; + error = npcm7xx_mii_setup(netdev); + if (error < 0) { + dev_err(&pdev->dev, "npcm7xx_mii_setup err\n"); + goto failed_free_napi; + } + } + + error = register_netdev(netdev); + if (error != 0) { + dev_err(&pdev->dev, "register_netdev() failed\n"); + error = -ENODEV; + goto failed_free_napi; + } + +#ifdef CONFIG_DEBUG_FS + npcm7xx_debug_fs(ether); +#endif + + return 0; + +failed_free_napi: + netif_napi_del(ðer->napi); + platform_set_drvdata(pdev, NULL); +failed_free_io: + iounmap(ether->reg); +failed_free_mem: + release_mem_region(ether->res->start, resource_size(ether->res)); +failed_free: + free_netdev(netdev); + + return error; +} + +static int npcm7xx_ether_remove(struct platform_device *pdev) +{ + struct net_device *netdev = platform_get_drvdata(pdev); + struct npcm7xx_ether *ether = netdev_priv(netdev); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(ether->dbgfs_dir); +#endif + + unregister_netdev(netdev); + + free_irq(ether->txirq, netdev); + free_irq(ether->rxirq, netdev); + + if (ether->phy_dev) + phy_disconnect(ether->phy_dev); + + mdiobus_unregister(ether->mii_bus); + kfree(ether->mii_bus->irq); + mdiobus_free(ether->mii_bus); + + platform_set_drvdata(pdev, NULL); + + free_netdev(netdev); + return 0; +} + +static struct platform_driver npcm7xx_ether_driver = { + .probe = npcm7xx_ether_probe, + .remove = npcm7xx_ether_remove, + .driver = { + .name = DRV_MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(emc_dt_id), + }, +}; + +module_platform_driver(npcm7xx_ether_driver); + +MODULE_AUTHOR("Nuvoton Technology Corp."); +MODULE_DESCRIPTION("NPCM750 EMC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:npcm750-emc"); +MODULE_VERSION(DRV_MODULE_VERSION); -- 2.18.0