diff options
Diffstat (limited to 'drivers/net/ethernet/nuvoton/w90p910_ether.c')
| -rw-r--r-- | drivers/net/ethernet/nuvoton/w90p910_ether.c | 1095 | 
1 files changed, 1095 insertions, 0 deletions
diff --git a/drivers/net/ethernet/nuvoton/w90p910_ether.c b/drivers/net/ethernet/nuvoton/w90p910_ether.c new file mode 100644 index 00000000000..79645f74b3a --- /dev/null +++ b/drivers/net/ethernet/nuvoton/w90p910_ether.c @@ -0,0 +1,1095 @@ +/* + * 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"); +  | 
