/* * Copyright (c) 2008-2009 Nuvoton technology corporation. * * Wan ZongShun <mcuos.com@gmail.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation;version 2 of the License. * */ #include <linux/module.h> #include <linux/init.h> #include <linux/mii.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/skbuff.h> #include <linux/ethtool.h> #include <linux/platform_device.h> #include <linux/clk.h> #include <linux/gfp.h> #define DRV_MODULE_NAME "w90p910-emc" #define DRV_MODULE_VERSION "0.1" /* 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_CTXDSA 0xcc #define REG_CTXBSA 0xd0 #define REG_CRXDSA 0xd4 #define REG_CRXBSA 0xd8 /* mac controller bit */ #define MCMDR_RXON 0x01 #define MCMDR_ACP (0x01 << 3) #define MCMDR_SPCRC (0x01 << 5) #define MCMDR_TXON (0x01 << 8) #define MCMDR_FDUP (0x01 << 18) #define MCMDR_ENMDC (0x01 << 19) #define MCMDR_OPMOD (0x01 << 20) #define SWR (0x01 << 24) /* cam command regiser */ #define CAMCMR_AUP 0x01 #define CAMCMR_AMP (0x01 << 1) #define CAMCMR_ABP (0x01 << 2) #define CAMCMR_CCAM (0x01 << 3) #define CAMCMR_ECMP (0x01 << 4) #define CAM0EN 0x01 /* mac mii controller bit */ #define MDCCR (0x0a << 20) #define PHYAD (0x01 << 8) #define PHYWR (0x01 << 16) #define PHYBUSY (0x01 << 17) #define PHYPRESP (0x01 << 18) #define CAM_ENTRY_SIZE 0x08 /* rx and tx status */ #define TXDS_TXCP (0x01 << 19) #define RXDS_CRCE (0x01 << 17) #define RXDS_PTLE (0x01 << 19) #define RXDS_RXGD (0x01 << 20) #define RXDS_ALIE (0x01 << 21) #define RXDS_RP (0x01 << 22) /* mac interrupt status*/ #define MISTA_EXDEF (0x01 << 19) #define MISTA_TXBERR (0x01 << 24) #define MISTA_TDU (0x01 << 23) #define MISTA_RDU (0x01 << 10) #define MISTA_RXBERR (0x01 << 11) #define ENSTART 0x01 #define ENRXINTR 0x01 #define ENRXGD (0x01 << 4) #define ENRXBERR (0x01 << 11) #define ENTXINTR (0x01 << 16) #define ENTXCP (0x01 << 18) #define ENTXABT (0x01 << 21) #define ENTXBERR (0x01 << 24) #define ENMDC (0x01 << 19) #define PHYBUSY (0x01 << 17) #define MDCCR_VAL 0xa00000 /* rx and tx owner bit */ #define RX_OWEN_DMA (0x01 << 31) #define RX_OWEN_CPU (~(0x03 << 30)) #define TX_OWEN_DMA (0x01 << 31) #define TX_OWEN_CPU (~(0x01 << 31)) /* tx frame desc controller bit */ #define MACTXINTEN 0x04 #define CRCMODE 0x02 #define PADDINGMODE 0x01 /* fftcr controller bit */ #define TXTHD (0x03 << 8) #define BLENGTH (0x01 << 20) /* global setting for driver */ #define RX_DESC_SIZE 50 #define TX_DESC_SIZE 10 #define MAX_RBUFF_SZ 0x600 #define MAX_TBUFF_SZ 0x600 #define TX_TIMEOUT (HZ/2) #define DELAY 1000 #define CAM0 0x0 static int w90p910_mdio_read(struct net_device *dev, int phy_id, int reg); struct w90p910_rxbd { unsigned int sl; unsigned int buffer; unsigned int reserved; unsigned int next; }; struct w90p910_txbd { unsigned int mode; unsigned int buffer; unsigned int sl; unsigned int next; }; struct recv_pdesc { struct w90p910_rxbd desclist[RX_DESC_SIZE]; char recv_buf[RX_DESC_SIZE][MAX_RBUFF_SZ]; }; struct tran_pdesc { struct w90p910_txbd desclist[TX_DESC_SIZE]; char tran_buf[TX_DESC_SIZE][MAX_TBUFF_SZ]; }; struct w90p910_ether { struct recv_pdesc *rdesc; struct tran_pdesc *tdesc; dma_addr_t rdesc_phys; dma_addr_t tdesc_phys; struct net_device_stats stats; struct platform_device *pdev; struct resource *res; struct sk_buff *skb; struct clk *clk; struct clk *rmiiclk; struct mii_if_info mii; struct timer_list check_timer; void __iomem *reg; int rxirq; int txirq; unsigned int cur_tx; unsigned int cur_rx; unsigned int finish_tx; unsigned int rx_packets; unsigned int rx_bytes; unsigned int start_tx_ptr; unsigned int start_rx_ptr; unsigned int linkflag; }; static void update_linkspeed_register(struct net_device *dev, unsigned int speed, unsigned int duplex) { struct w90p910_ether *ether = netdev_priv(dev); unsigned int val; val = __raw_readl(ether->reg + REG_MCMDR); if (speed == SPEED_100) { /* 100 full/half duplex */ if (duplex == DUPLEX_FULL) { val |= (MCMDR_OPMOD | MCMDR_FDUP); } else { val |= MCMDR_OPMOD; val &= ~MCMDR_FDUP; } } else { /* 10 full/half duplex */ if (duplex == DUPLEX_FULL) { val |= MCMDR_FDUP; val &= ~MCMDR_OPMOD; } else { val &= ~(MCMDR_FDUP | MCMDR_OPMOD); } } __raw_writel(val, ether->reg + REG_MCMDR); } static void update_linkspeed(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); struct platform_device *pdev; unsigned int bmsr, bmcr, lpa, speed, duplex; pdev = ether->pdev; if (!mii_link_ok(ðer->mii)) { ether->linkflag = 0x0; netif_carrier_off(dev); dev_warn(&pdev->dev, "%s: Link down.\n", dev->name); return; } if (ether->linkflag == 1) return; bmsr = w90p910_mdio_read(dev, ether->mii.phy_id, MII_BMSR); bmcr = w90p910_mdio_read(dev, ether->mii.phy_id, MII_BMCR); if (bmcr & BMCR_ANENABLE) { if (!(bmsr & BMSR_ANEGCOMPLETE)) return; lpa = w90p910_mdio_read(dev, ether->mii.phy_id, MII_LPA); if ((lpa & LPA_100FULL) || (lpa & LPA_100HALF)) speed = SPEED_100; else speed = SPEED_10; if ((lpa & LPA_100FULL) || (lpa & LPA_10FULL)) duplex = DUPLEX_FULL; else duplex = DUPLEX_HALF; } else { speed = (bmcr & BMCR_SPEED100) ? SPEED_100 : SPEED_10; duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF; } update_linkspeed_register(dev, speed, duplex); dev_info(&pdev->dev, "%s: Link now %i-%s\n", dev->name, speed, (duplex == DUPLEX_FULL) ? "FullDuplex" : "HalfDuplex"); ether->linkflag = 0x01; netif_carrier_on(dev); } static void w90p910_check_link(unsigned long dev_id) { struct net_device *dev = (struct net_device *) dev_id; struct w90p910_ether *ether = netdev_priv(dev); update_linkspeed(dev); mod_timer(ðer->check_timer, jiffies + msecs_to_jiffies(1000)); } static void w90p910_write_cam(struct net_device *dev, unsigned int x, unsigned char *pval) { struct w90p910_ether *ether = netdev_priv(dev); unsigned int msw, lsw; msw = (pval[0] << 24) | (pval[1] << 16) | (pval[2] << 8) | pval[3]; lsw = (pval[4] << 24) | (pval[5] << 16); __raw_writel(lsw, ether->reg + REG_CAML_BASE + x * CAM_ENTRY_SIZE); __raw_writel(msw, ether->reg + REG_CAMM_BASE + x * CAM_ENTRY_SIZE); } static int w90p910_init_desc(struct net_device *dev) { struct w90p910_ether *ether; struct w90p910_txbd *tdesc; struct w90p910_rxbd *rdesc; struct platform_device *pdev; unsigned int i; ether = netdev_priv(dev); pdev = ether->pdev; ether->tdesc = dma_alloc_coherent(&pdev->dev, sizeof(struct tran_pdesc), ðer->tdesc_phys, GFP_KERNEL); if (!ether->tdesc) return -ENOMEM; ether->rdesc = dma_alloc_coherent(&pdev->dev, sizeof(struct recv_pdesc), ðer->rdesc_phys, GFP_KERNEL); if (!ether->rdesc) { dma_free_coherent(&pdev->dev, sizeof(struct tran_pdesc), ether->tdesc, ether->tdesc_phys); return -ENOMEM; } for (i = 0; i < TX_DESC_SIZE; i++) { unsigned int offset; tdesc = &(ether->tdesc->desclist[i]); if (i == TX_DESC_SIZE - 1) offset = offsetof(struct tran_pdesc, desclist[0]); else offset = offsetof(struct tran_pdesc, desclist[i + 1]); tdesc->next = ether->tdesc_phys + offset; tdesc->buffer = ether->tdesc_phys + offsetof(struct tran_pdesc, tran_buf[i]); tdesc->sl = 0; tdesc->mode = 0; } ether->start_tx_ptr = ether->tdesc_phys; for (i = 0; i < RX_DESC_SIZE; i++) { unsigned int offset; rdesc = &(ether->rdesc->desclist[i]); if (i == RX_DESC_SIZE - 1) offset = offsetof(struct recv_pdesc, desclist[0]); else offset = offsetof(struct recv_pdesc, desclist[i + 1]); rdesc->next = ether->rdesc_phys + offset; rdesc->sl = RX_OWEN_DMA; rdesc->buffer = ether->rdesc_phys + offsetof(struct recv_pdesc, recv_buf[i]); } ether->start_rx_ptr = ether->rdesc_phys; return 0; } static void w90p910_set_fifo_threshold(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); unsigned int val; val = TXTHD | BLENGTH; __raw_writel(val, ether->reg + REG_FFTCR); } static void w90p910_return_default_idle(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); unsigned int val; val = __raw_readl(ether->reg + REG_MCMDR); val |= SWR; __raw_writel(val, ether->reg + REG_MCMDR); } static void w90p910_trigger_rx(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); __raw_writel(ENSTART, ether->reg + REG_RSDR); } static void w90p910_trigger_tx(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); __raw_writel(ENSTART, ether->reg + REG_TSDR); } static void w90p910_enable_mac_interrupt(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); unsigned int val; val = ENTXINTR | ENRXINTR | ENRXGD | ENTXCP; val |= ENTXBERR | ENRXBERR | ENTXABT; __raw_writel(val, ether->reg + REG_MIEN); } static void w90p910_get_and_clear_int(struct net_device *dev, unsigned int *val) { struct w90p910_ether *ether = netdev_priv(dev); *val = __raw_readl(ether->reg + REG_MISTA); __raw_writel(*val, ether->reg + REG_MISTA); } static void w90p910_set_global_maccmd(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); unsigned int val; val = __raw_readl(ether->reg + REG_MCMDR); val |= MCMDR_SPCRC | MCMDR_ENMDC | MCMDR_ACP | ENMDC; __raw_writel(val, ether->reg + REG_MCMDR); } static void w90p910_enable_cam(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); unsigned int val; w90p910_write_cam(dev, CAM0, dev->dev_addr); val = __raw_readl(ether->reg + REG_CAMEN); val |= CAM0EN; __raw_writel(val, ether->reg + REG_CAMEN); } static void w90p910_enable_cam_command(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); unsigned int val; val = CAMCMR_ECMP | CAMCMR_ABP | CAMCMR_AMP; __raw_writel(val, ether->reg + REG_CAMCMR); } static void w90p910_enable_tx(struct net_device *dev, unsigned int enable) { struct w90p910_ether *ether = netdev_priv(dev); unsigned int val; val = __raw_readl(ether->reg + REG_MCMDR); if (enable) val |= MCMDR_TXON; else val &= ~MCMDR_TXON; __raw_writel(val, ether->reg + REG_MCMDR); } static void w90p910_enable_rx(struct net_device *dev, unsigned int enable) { struct w90p910_ether *ether = netdev_priv(dev); unsigned int val; val = __raw_readl(ether->reg + REG_MCMDR); if (enable) val |= MCMDR_RXON; else val &= ~MCMDR_RXON; __raw_writel(val, ether->reg + REG_MCMDR); } static void w90p910_set_curdest(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); __raw_writel(ether->start_rx_ptr, ether->reg + REG_RXDLSA); __raw_writel(ether->start_tx_ptr, ether->reg + REG_TXDLSA); } static void w90p910_reset_mac(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); w90p910_enable_tx(dev, 0); w90p910_enable_rx(dev, 0); w90p910_set_fifo_threshold(dev); w90p910_return_default_idle(dev); if (!netif_queue_stopped(dev)) netif_stop_queue(dev); w90p910_init_desc(dev); dev->trans_start = jiffies; /* prevent tx timeout */ ether->cur_tx = 0x0; ether->finish_tx = 0x0; ether->cur_rx = 0x0; w90p910_set_curdest(dev); w90p910_enable_cam(dev); w90p910_enable_cam_command(dev); w90p910_enable_mac_interrupt(dev); w90p910_enable_tx(dev, 1); w90p910_enable_rx(dev, 1); w90p910_trigger_tx(dev); w90p910_trigger_rx(dev); dev->trans_start = jiffies; /* prevent tx timeout */ if (netif_queue_stopped(dev)) netif_wake_queue(dev); } static void w90p910_mdio_write(struct net_device *dev, int phy_id, int reg, int data) { struct w90p910_ether *ether = netdev_priv(dev); struct platform_device *pdev; unsigned int val, i; pdev = ether->pdev; __raw_writel(data, ether->reg + REG_MIID); val = (phy_id << 0x08) | reg; val |= PHYBUSY | PHYWR | MDCCR_VAL; __raw_writel(val, ether->reg + REG_MIIDA); for (i = 0; i < DELAY; i++) { if ((__raw_readl(ether->reg + REG_MIIDA) & PHYBUSY) == 0) break; } if (i == DELAY) dev_warn(&pdev->dev, "mdio write timed out\n"); } static int w90p910_mdio_read(struct net_device *dev, int phy_id, int reg) { struct w90p910_ether *ether = netdev_priv(dev); struct platform_device *pdev; unsigned int val, i, data; pdev = ether->pdev; val = (phy_id << 0x08) | reg; val |= PHYBUSY | MDCCR_VAL; __raw_writel(val, ether->reg + REG_MIIDA); for (i = 0; i < DELAY; i++) { if ((__raw_readl(ether->reg + REG_MIIDA) & PHYBUSY) == 0) break; } if (i == DELAY) { dev_warn(&pdev->dev, "mdio read timed out\n"); data = 0xffff; } else { data = __raw_readl(ether->reg + REG_MIID); } return data; } static int w90p910_set_mac_address(struct net_device *dev, void *addr) { struct sockaddr *address = addr; if (!is_valid_ether_addr(address->sa_data)) return -EADDRNOTAVAIL; memcpy(dev->dev_addr, address->sa_data, dev->addr_len); w90p910_write_cam(dev, CAM0, dev->dev_addr); return 0; } static int w90p910_ether_close(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); struct platform_device *pdev; pdev = ether->pdev; dma_free_coherent(&pdev->dev, sizeof(struct recv_pdesc), ether->rdesc, ether->rdesc_phys); dma_free_coherent(&pdev->dev, sizeof(struct tran_pdesc), ether->tdesc, ether->tdesc_phys); netif_stop_queue(dev); del_timer_sync(ðer->check_timer); clk_disable(ether->rmiiclk); clk_disable(ether->clk); free_irq(ether->txirq, dev); free_irq(ether->rxirq, dev); return 0; } static struct net_device_stats *w90p910_ether_stats(struct net_device *dev) { struct w90p910_ether *ether; ether = netdev_priv(dev); return ðer->stats; } static int w90p910_send_frame(struct net_device *dev, unsigned char *data, int length) { struct w90p910_ether *ether; struct w90p910_txbd *txbd; struct platform_device *pdev; unsigned char *buffer; ether = netdev_priv(dev); pdev = ether->pdev; txbd = ðer->tdesc->desclist[ether->cur_tx]; buffer = ether->tdesc->tran_buf[ether->cur_tx]; if (length > 1514) { dev_err(&pdev->dev, "send data %d bytes, check it\n", length); length = 1514; } txbd->sl = length & 0xFFFF; memcpy(buffer, data, length); txbd->mode = TX_OWEN_DMA | PADDINGMODE | CRCMODE | MACTXINTEN; w90p910_enable_tx(dev, 1); w90p910_trigger_tx(dev); if (++ether->cur_tx >= TX_DESC_SIZE) ether->cur_tx = 0; txbd = ðer->tdesc->desclist[ether->cur_tx]; if (txbd->mode & TX_OWEN_DMA) netif_stop_queue(dev); return 0; } static int w90p910_ether_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); if (!(w90p910_send_frame(dev, skb->data, skb->len))) { ether->skb = skb; dev_kfree_skb_irq(skb); return 0; } return -EAGAIN; } static irqreturn_t w90p910_tx_interrupt(int irq, void *dev_id) { struct w90p910_ether *ether; struct w90p910_txbd *txbd; struct platform_device *pdev; struct net_device *dev; unsigned int cur_entry, entry, status; dev = dev_id; ether = netdev_priv(dev); pdev = ether->pdev; w90p910_get_and_clear_int(dev, &status); cur_entry = __raw_readl(ether->reg + REG_CTXDSA); entry = ether->tdesc_phys + offsetof(struct tran_pdesc, desclist[ether->finish_tx]); while (entry != cur_entry) { txbd = ðer->tdesc->desclist[ether->finish_tx]; if (++ether->finish_tx >= TX_DESC_SIZE) ether->finish_tx = 0; if (txbd->sl & TXDS_TXCP) { ether->stats.tx_packets++; ether->stats.tx_bytes += txbd->sl & 0xFFFF; } else { ether->stats.tx_errors++; } txbd->sl = 0x0; txbd->mode = 0x0; if (netif_queue_stopped(dev)) netif_wake_queue(dev); entry = ether->tdesc_phys + offsetof(struct tran_pdesc, desclist[ether->finish_tx]); } if (status & MISTA_EXDEF) { dev_err(&pdev->dev, "emc defer exceed interrupt\n"); } else if (status & MISTA_TXBERR) { dev_err(&pdev->dev, "emc bus error interrupt\n"); w90p910_reset_mac(dev); } else if (status & MISTA_TDU) { if (netif_queue_stopped(dev)) netif_wake_queue(dev); } return IRQ_HANDLED; } static void netdev_rx(struct net_device *dev) { struct w90p910_ether *ether; struct w90p910_rxbd *rxbd; struct platform_device *pdev; struct sk_buff *skb; unsigned char *data; unsigned int length, status, val, entry; ether = netdev_priv(dev); pdev = ether->pdev; rxbd = ðer->rdesc->desclist[ether->cur_rx]; do { val = __raw_readl(ether->reg + REG_CRXDSA); entry = ether->rdesc_phys + offsetof(struct recv_pdesc, desclist[ether->cur_rx]); if (val == entry) break; status = rxbd->sl; length = status & 0xFFFF; if (status & RXDS_RXGD) { data = ether->rdesc->recv_buf[ether->cur_rx]; skb = netdev_alloc_skb(dev, length + 2); if (!skb) { ether->stats.rx_dropped++; return; } skb_reserve(skb, 2); skb_put(skb, length); skb_copy_to_linear_data(skb, data, length); skb->protocol = eth_type_trans(skb, dev); ether->stats.rx_packets++; ether->stats.rx_bytes += length; netif_rx(skb); } else { ether->stats.rx_errors++; if (status & RXDS_RP) { dev_err(&pdev->dev, "rx runt err\n"); ether->stats.rx_length_errors++; } else if (status & RXDS_CRCE) { dev_err(&pdev->dev, "rx crc err\n"); ether->stats.rx_crc_errors++; } else if (status & RXDS_ALIE) { dev_err(&pdev->dev, "rx aligment err\n"); ether->stats.rx_frame_errors++; } else if (status & RXDS_PTLE) { dev_err(&pdev->dev, "rx longer err\n"); ether->stats.rx_over_errors++; } } rxbd->sl = RX_OWEN_DMA; rxbd->reserved = 0x0; if (++ether->cur_rx >= RX_DESC_SIZE) ether->cur_rx = 0; rxbd = ðer->rdesc->desclist[ether->cur_rx]; } while (1); } static irqreturn_t w90p910_rx_interrupt(int irq, void *dev_id) { struct net_device *dev; struct w90p910_ether *ether; struct platform_device *pdev; unsigned int status; dev = dev_id; ether = netdev_priv(dev); pdev = ether->pdev; w90p910_get_and_clear_int(dev, &status); if (status & MISTA_RDU) { netdev_rx(dev); w90p910_trigger_rx(dev); return IRQ_HANDLED; } else if (status & MISTA_RXBERR) { dev_err(&pdev->dev, "emc rx bus error\n"); w90p910_reset_mac(dev); } netdev_rx(dev); return IRQ_HANDLED; } static int w90p910_ether_open(struct net_device *dev) { struct w90p910_ether *ether; struct platform_device *pdev; ether = netdev_priv(dev); pdev = ether->pdev; w90p910_reset_mac(dev); w90p910_set_fifo_threshold(dev); w90p910_set_curdest(dev); w90p910_enable_cam(dev); w90p910_enable_cam_command(dev); w90p910_enable_mac_interrupt(dev); w90p910_set_global_maccmd(dev); w90p910_enable_rx(dev, 1); clk_enable(ether->rmiiclk); clk_enable(ether->clk); ether->rx_packets = 0x0; ether->rx_bytes = 0x0; if (request_irq(ether->txirq, w90p910_tx_interrupt, 0x0, pdev->name, dev)) { dev_err(&pdev->dev, "register irq tx failed\n"); return -EAGAIN; } if (request_irq(ether->rxirq, w90p910_rx_interrupt, 0x0, pdev->name, dev)) { dev_err(&pdev->dev, "register irq rx failed\n"); free_irq(ether->txirq, dev); return -EAGAIN; } mod_timer(ðer->check_timer, jiffies + msecs_to_jiffies(1000)); netif_start_queue(dev); w90p910_trigger_rx(dev); dev_info(&pdev->dev, "%s is OPENED\n", dev->name); return 0; } static void w90p910_ether_set_multicast_list(struct net_device *dev) { struct w90p910_ether *ether; unsigned int rx_mode; ether = netdev_priv(dev); if (dev->flags & IFF_PROMISC) rx_mode = CAMCMR_AUP | CAMCMR_AMP | CAMCMR_ABP | CAMCMR_ECMP; else if ((dev->flags & IFF_ALLMULTI) || !netdev_mc_empty(dev)) rx_mode = CAMCMR_AMP | CAMCMR_ABP | CAMCMR_ECMP; else rx_mode = CAMCMR_ECMP | CAMCMR_ABP; __raw_writel(rx_mode, ether->reg + REG_CAMCMR); } static int w90p910_ether_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { struct w90p910_ether *ether = netdev_priv(dev); struct mii_ioctl_data *data = if_mii(ifr); return generic_mii_ioctl(ðer->mii, data, cmd, NULL); } static void w90p910_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) { strlcpy(info->driver, DRV_MODULE_NAME, sizeof(info->driver)); strlcpy(info->version, DRV_MODULE_VERSION, sizeof(info->version)); } static int w90p910_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) { struct w90p910_ether *ether = netdev_priv(dev); return mii_ethtool_gset(ðer->mii, cmd); } static int w90p910_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) { struct w90p910_ether *ether = netdev_priv(dev); return mii_ethtool_sset(ðer->mii, cmd); } static int w90p910_nway_reset(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); return mii_nway_restart(ðer->mii); } static u32 w90p910_get_link(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); return mii_link_ok(ðer->mii); } static const struct ethtool_ops w90p910_ether_ethtool_ops = { .get_settings = w90p910_get_settings, .set_settings = w90p910_set_settings, .get_drvinfo = w90p910_get_drvinfo, .nway_reset = w90p910_nway_reset, .get_link = w90p910_get_link, }; static const struct net_device_ops w90p910_ether_netdev_ops = { .ndo_open = w90p910_ether_open, .ndo_stop = w90p910_ether_close, .ndo_start_xmit = w90p910_ether_start_xmit, .ndo_get_stats = w90p910_ether_stats, .ndo_set_rx_mode = w90p910_ether_set_multicast_list, .ndo_set_mac_address = w90p910_set_mac_address, .ndo_do_ioctl = w90p910_ether_ioctl, .ndo_validate_addr = eth_validate_addr, .ndo_change_mtu = eth_change_mtu, }; static void __init get_mac_address(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); struct platform_device *pdev; char addr[ETH_ALEN]; pdev = ether->pdev; addr[0] = 0x00; addr[1] = 0x02; addr[2] = 0xac; addr[3] = 0x55; addr[4] = 0x88; addr[5] = 0xa8; if (is_valid_ether_addr(addr)) memcpy(dev->dev_addr, &addr, ETH_ALEN); else dev_err(&pdev->dev, "invalid mac address\n"); } static int w90p910_ether_setup(struct net_device *dev) { struct w90p910_ether *ether = netdev_priv(dev); ether_setup(dev); dev->netdev_ops = &w90p910_ether_netdev_ops; dev->ethtool_ops = &w90p910_ether_ethtool_ops; dev->tx_queue_len = 16; dev->dma = 0x0; dev->watchdog_timeo = TX_TIMEOUT; get_mac_address(dev); ether->cur_tx = 0x0; ether->cur_rx = 0x0; ether->finish_tx = 0x0; ether->linkflag = 0x0; ether->mii.phy_id = 0x01; ether->mii.phy_id_mask = 0x1f; ether->mii.reg_num_mask = 0x1f; ether->mii.dev = dev; ether->mii.mdio_read = w90p910_mdio_read; ether->mii.mdio_write = w90p910_mdio_write; setup_timer(ðer->check_timer, w90p910_check_link, (unsigned long)dev); return 0; } static int w90p910_ether_probe(struct platform_device *pdev) { struct w90p910_ether *ether; struct net_device *dev; int error; dev = alloc_etherdev(sizeof(struct w90p910_ether)); if (!dev) return -ENOMEM; ether = netdev_priv(dev); ether->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (ether->res == NULL) { 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)); if (ether->reg == NULL) { 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; } platform_set_drvdata(pdev, dev); ether->clk = clk_get(&pdev->dev, NULL); if (IS_ERR(ether->clk)) { dev_err(&pdev->dev, "failed to get ether clock\n"); error = PTR_ERR(ether->clk); goto failed_free_io; } ether->rmiiclk = clk_get(&pdev->dev, "RMII"); if (IS_ERR(ether->rmiiclk)) { dev_err(&pdev->dev, "failed to get ether clock\n"); error = PTR_ERR(ether->rmiiclk); goto failed_put_clk; } ether->pdev = pdev; w90p910_ether_setup(dev); error = register_netdev(dev); if (error != 0) { dev_err(&pdev->dev, "Regiter EMC w90p910 FAILED\n"); error = -ENODEV; goto failed_put_rmiiclk; } return 0; failed_put_rmiiclk: clk_put(ether->rmiiclk); failed_put_clk: clk_put(ether->clk); failed_free_io: iounmap(ether->reg); failed_free_mem: release_mem_region(ether->res->start, resource_size(ether->res)); failed_free: free_netdev(dev); return error; } static int w90p910_ether_remove(struct platform_device *pdev) { struct net_device *dev = platform_get_drvdata(pdev); struct w90p910_ether *ether = netdev_priv(dev); unregister_netdev(dev); clk_put(ether->rmiiclk); clk_put(ether->clk); iounmap(ether->reg); release_mem_region(ether->res->start, resource_size(ether->res)); del_timer_sync(ðer->check_timer); free_netdev(dev); return 0; } static struct platform_driver w90p910_ether_driver = { .probe = w90p910_ether_probe, .remove = w90p910_ether_remove, .driver = { .name = "nuc900-emc", .owner = THIS_MODULE, }, }; module_platform_driver(w90p910_ether_driver); MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); MODULE_DESCRIPTION("w90p910 MAC driver!"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:nuc900-emc");