tsi108_eth.c

来自「linux 内核源代码」· C语言 代码 · 共 1,704 行 · 第 1/4 页

C
1,704
字号
/*******************************************************************************  Copyright(c) 2006 Tundra Semiconductor Corporation.  This program is free software; you can redistribute it and/or modify it  under the terms of the GNU General Public License as published by the Free  Software Foundation; either version 2 of the License, or (at your option)  any later version.  This program is distributed in the hope that it will be useful, but WITHOUT  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for  more details.  You should have received a copy of the GNU General Public License along with  this program; if not, write to the Free Software Foundation, Inc., 59  Temple Place - Suite 330, Boston, MA  02111-1307, USA.*******************************************************************************//* This driver is based on the driver code originally developed * for the Intel IOC80314 (ForestLake) Gigabit Ethernet by * scott.wood@timesys.com  * Copyright (C) 2003 TimeSys Corporation * * Currently changes from original version are: * - porting to Tsi108-based platform and kernel 2.6 (kong.lai@tundra.com) * - modifications to handle two ports independently and support for *   additional PHY devices (alexandre.bounine@tundra.com) * - Get hardware information from platform device. (tie-fei.zang@freescale.com) * */#include <linux/module.h>#include <linux/types.h>#include <linux/init.h>#include <linux/net.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/skbuff.h>#include <linux/slab.h>#include <linux/spinlock.h>#include <linux/delay.h>#include <linux/crc32.h>#include <linux/mii.h>#include <linux/device.h>#include <linux/pci.h>#include <linux/rtnetlink.h>#include <linux/timer.h>#include <linux/platform_device.h>#include <asm/system.h>#include <asm/io.h>#include <asm/tsi108.h>#include "tsi108_eth.h"#define MII_READ_DELAY 10000	/* max link wait time in msec */#define TSI108_RXRING_LEN     256/* NOTE: The driver currently does not support receiving packets * larger than the buffer size, so don't decrease this (unless you * want to add such support). */#define TSI108_RXBUF_SIZE     1536#define TSI108_TXRING_LEN     256#define TSI108_TX_INT_FREQ    64/* Check the phy status every half a second. */#define CHECK_PHY_INTERVAL (HZ/2)static int tsi108_init_one(struct platform_device *pdev);static int tsi108_ether_remove(struct platform_device *pdev);struct tsi108_prv_data {	void  __iomem *regs;	/* Base of normal regs */	void  __iomem *phyregs;	/* Base of register bank used for PHY access */	struct net_device *dev;	struct napi_struct napi;	unsigned int phy;		/* Index of PHY for this interface */	unsigned int irq_num;	unsigned int id;	unsigned int phy_type;	struct timer_list timer;/* Timer that triggers the check phy function */	unsigned int rxtail;	/* Next entry in rxring to read */	unsigned int rxhead;	/* Next entry in rxring to give a new buffer */	unsigned int rxfree;	/* Number of free, allocated RX buffers */	unsigned int rxpending;	/* Non-zero if there are still descriptors				 * to be processed from a previous descriptor				 * interrupt condition that has been cleared */	unsigned int txtail;	/* Next TX descriptor to check status on */	unsigned int txhead;	/* Next TX descriptor to use */	/* Number of free TX descriptors.  This could be calculated from	 * rxhead and rxtail if one descriptor were left unused to disambiguate	 * full and empty conditions, but it's simpler to just keep track	 * explicitly. */	unsigned int txfree;	unsigned int phy_ok;		/* The PHY is currently powered on. */	/* PHY status (duplex is 1 for half, 2 for full,	 * so that the default 0 indicates that neither has	 * yet been configured). */	unsigned int link_up;	unsigned int speed;	unsigned int duplex;	tx_desc *txring;	rx_desc *rxring;	struct sk_buff *txskbs[TSI108_TXRING_LEN];	struct sk_buff *rxskbs[TSI108_RXRING_LEN];	dma_addr_t txdma, rxdma;	/* txlock nests in misclock and phy_lock */	spinlock_t txlock, misclock;	/* stats is used to hold the upper bits of each hardware counter,	 * and tmpstats is used to hold the full values for returning	 * to the caller of get_stats().  They must be separate in case	 * an overflow interrupt occurs before the stats are consumed.	 */	struct net_device_stats stats;	struct net_device_stats tmpstats;	/* These stats are kept separate in hardware, thus require individual	 * fields for handling carry.  They are combined in get_stats.	 */	unsigned long rx_fcs;	/* Add to rx_frame_errors */	unsigned long rx_short_fcs;	/* Add to rx_frame_errors */	unsigned long rx_long_fcs;	/* Add to rx_frame_errors */	unsigned long rx_underruns;	/* Add to rx_length_errors */	unsigned long rx_overruns;	/* Add to rx_length_errors */	unsigned long tx_coll_abort;	/* Add to tx_aborted_errors/collisions */	unsigned long tx_pause_drop;	/* Add to tx_aborted_errors */	unsigned long mc_hash[16];	u32 msg_enable;			/* debug message level */	struct mii_if_info mii_if;	unsigned int init_media;};/* Structure for a device driver */static struct platform_driver tsi_eth_driver = {	.probe = tsi108_init_one,	.remove = tsi108_ether_remove,	.driver	= {		.name = "tsi-ethernet",	},};static void tsi108_timed_checker(unsigned long dev_ptr);static void dump_eth_one(struct net_device *dev){	struct tsi108_prv_data *data = netdev_priv(dev);	printk("Dumping %s...\n", dev->name);	printk("intstat %x intmask %x phy_ok %d"	       " link %d speed %d duplex %d\n",	       TSI_READ(TSI108_EC_INTSTAT),	       TSI_READ(TSI108_EC_INTMASK), data->phy_ok,	       data->link_up, data->speed, data->duplex);	printk("TX: head %d, tail %d, free %d, stat %x, estat %x, err %x\n",	       data->txhead, data->txtail, data->txfree,	       TSI_READ(TSI108_EC_TXSTAT),	       TSI_READ(TSI108_EC_TXESTAT),	       TSI_READ(TSI108_EC_TXERR));	printk("RX: head %d, tail %d, free %d, stat %x,"	       " estat %x, err %x, pending %d\n\n",	       data->rxhead, data->rxtail, data->rxfree,	       TSI_READ(TSI108_EC_RXSTAT),	       TSI_READ(TSI108_EC_RXESTAT),	       TSI_READ(TSI108_EC_RXERR), data->rxpending);}/* Synchronization is needed between the thread and up/down events. * Note that the PHY is accessed through the same registers for both * interfaces, so this can't be made interface-specific. */static DEFINE_SPINLOCK(phy_lock);static int tsi108_read_mii(struct tsi108_prv_data *data, int reg){	unsigned i;	TSI_WRITE_PHY(TSI108_MAC_MII_ADDR,				(data->phy << TSI108_MAC_MII_ADDR_PHY) |				(reg << TSI108_MAC_MII_ADDR_REG));	TSI_WRITE_PHY(TSI108_MAC_MII_CMD, 0);	TSI_WRITE_PHY(TSI108_MAC_MII_CMD, TSI108_MAC_MII_CMD_READ);	for (i = 0; i < 100; i++) {		if (!(TSI_READ_PHY(TSI108_MAC_MII_IND) &		      (TSI108_MAC_MII_IND_NOTVALID | TSI108_MAC_MII_IND_BUSY)))			break;		udelay(10);	}	if (i == 100)		return 0xffff;	else		return (TSI_READ_PHY(TSI108_MAC_MII_DATAIN));}static void tsi108_write_mii(struct tsi108_prv_data *data,				int reg, u16 val){	unsigned i = 100;	TSI_WRITE_PHY(TSI108_MAC_MII_ADDR,				(data->phy << TSI108_MAC_MII_ADDR_PHY) |				(reg << TSI108_MAC_MII_ADDR_REG));	TSI_WRITE_PHY(TSI108_MAC_MII_DATAOUT, val);	while (i--) {		if(!(TSI_READ_PHY(TSI108_MAC_MII_IND) &			TSI108_MAC_MII_IND_BUSY))			break;		udelay(10);	}}static int tsi108_mdio_read(struct net_device *dev, int addr, int reg){	struct tsi108_prv_data *data = netdev_priv(dev);	return tsi108_read_mii(data, reg);}static void tsi108_mdio_write(struct net_device *dev, int addr, int reg, int val){	struct tsi108_prv_data *data = netdev_priv(dev);	tsi108_write_mii(data, reg, val);}static inline void tsi108_write_tbi(struct tsi108_prv_data *data,					int reg, u16 val){	unsigned i = 1000;	TSI_WRITE(TSI108_MAC_MII_ADDR,			     (0x1e << TSI108_MAC_MII_ADDR_PHY)			     | (reg << TSI108_MAC_MII_ADDR_REG));	TSI_WRITE(TSI108_MAC_MII_DATAOUT, val);	while(i--) {		if(!(TSI_READ(TSI108_MAC_MII_IND) & TSI108_MAC_MII_IND_BUSY))			return;		udelay(10);	}	printk(KERN_ERR "%s function time out \n", __FUNCTION__);}static int mii_speed(struct mii_if_info *mii){	int advert, lpa, val, media;	int lpa2 = 0;	int speed;	if (!mii_link_ok(mii))		return 0;	val = (*mii->mdio_read) (mii->dev, mii->phy_id, MII_BMSR);	if ((val & BMSR_ANEGCOMPLETE) == 0)		return 0;	advert = (*mii->mdio_read) (mii->dev, mii->phy_id, MII_ADVERTISE);	lpa = (*mii->mdio_read) (mii->dev, mii->phy_id, MII_LPA);	media = mii_nway_result(advert & lpa);	if (mii->supports_gmii)		lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);	speed = lpa2 & (LPA_1000FULL | LPA_1000HALF) ? 1000 :			(media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ? 100 : 10);	return speed;}static void tsi108_check_phy(struct net_device *dev){	struct tsi108_prv_data *data = netdev_priv(dev);	u32 mac_cfg2_reg, portctrl_reg;	u32 duplex;	u32 speed;	unsigned long flags;	/* Do a dummy read, as for some reason the first read	 * after a link becomes up returns link down, even if	 * it's been a while since the link came up.	 */	spin_lock_irqsave(&phy_lock, flags);	if (!data->phy_ok)		goto out;	tsi108_read_mii(data, MII_BMSR);	duplex = mii_check_media(&data->mii_if, netif_msg_link(data), data->init_media);	data->init_media = 0;	if (netif_carrier_ok(dev)) {		speed = mii_speed(&data->mii_if);		if ((speed != data->speed) || duplex) {			mac_cfg2_reg = TSI_READ(TSI108_MAC_CFG2);			portctrl_reg = TSI_READ(TSI108_EC_PORTCTRL);			mac_cfg2_reg &= ~TSI108_MAC_CFG2_IFACE_MASK;			if (speed == 1000) {				mac_cfg2_reg |= TSI108_MAC_CFG2_GIG;				portctrl_reg &= ~TSI108_EC_PORTCTRL_NOGIG;			} else {				mac_cfg2_reg |= TSI108_MAC_CFG2_NOGIG;				portctrl_reg |= TSI108_EC_PORTCTRL_NOGIG;			}			data->speed = speed;			if (data->mii_if.full_duplex) {				mac_cfg2_reg |= TSI108_MAC_CFG2_FULLDUPLEX;				portctrl_reg &= ~TSI108_EC_PORTCTRL_HALFDUPLEX;				data->duplex = 2;			} else {				mac_cfg2_reg &= ~TSI108_MAC_CFG2_FULLDUPLEX;				portctrl_reg |= TSI108_EC_PORTCTRL_HALFDUPLEX;				data->duplex = 1;			}			TSI_WRITE(TSI108_MAC_CFG2, mac_cfg2_reg);			TSI_WRITE(TSI108_EC_PORTCTRL, portctrl_reg);			if (data->link_up == 0) {				/* The manual says it can take 3-4 usecs for the speed change				 * to take effect.				 */				udelay(5);				spin_lock(&data->txlock);				if (is_valid_ether_addr(dev->dev_addr) && data->txfree)					netif_wake_queue(dev);				data->link_up = 1;				spin_unlock(&data->txlock);			}		}	} else {		if (data->link_up == 1) {			netif_stop_queue(dev);			data->link_up = 0;			printk(KERN_NOTICE "%s : link is down\n", dev->name);		}		goto out;	}out:	spin_unlock_irqrestore(&phy_lock, flags);}static inline voidtsi108_stat_carry_one(int carry, int carry_bit, int carry_shift,		      unsigned long *upper){	if (carry & carry_bit)		*upper += carry_shift;}static void tsi108_stat_carry(struct net_device *dev){	struct tsi108_prv_data *data = netdev_priv(dev);	u32 carry1, carry2;	spin_lock_irq(&data->misclock);	carry1 = TSI_READ(TSI108_STAT_CARRY1);	carry2 = TSI_READ(TSI108_STAT_CARRY2);	TSI_WRITE(TSI108_STAT_CARRY1, carry1);	TSI_WRITE(TSI108_STAT_CARRY2, carry2);	tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXBYTES,			      TSI108_STAT_RXBYTES_CARRY, &data->stats.rx_bytes);	tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXPKTS,			      TSI108_STAT_RXPKTS_CARRY,			      &data->stats.rx_packets);	tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXFCS,			      TSI108_STAT_RXFCS_CARRY, &data->rx_fcs);	tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXMCAST,			      TSI108_STAT_RXMCAST_CARRY,			      &data->stats.multicast);	tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXALIGN,			      TSI108_STAT_RXALIGN_CARRY,			      &data->stats.rx_frame_errors);	tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXLENGTH,			      TSI108_STAT_RXLENGTH_CARRY,			      &data->stats.rx_length_errors);	tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXRUNT,			      TSI108_STAT_RXRUNT_CARRY, &data->rx_underruns);	tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXJUMBO,			      TSI108_STAT_RXJUMBO_CARRY, &data->rx_overruns);

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?