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 + -
显示快捷键?