aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/ethernet/sis
diff options
context:
space:
mode:
authorJeff Kirsher <jeffrey.t.kirsher@intel.com>2011-06-13 08:43:49 -0700
committerJeff Kirsher <jeffrey.t.kirsher@intel.com>2011-08-12 03:40:44 -0700
commit8c7de408fd2caa11b8b3a1ed8a1b141ce127ad4e (patch)
tree19886abfa361eebc0113143263e593909b88b259 /drivers/net/ethernet/sis
parentcnic: Fix select dependencies in bnx2fc/bnx2i Kconfig. (diff)
downloadlinux-dev-8c7de408fd2caa11b8b3a1ed8a1b141ce127ad4e.tar.xz
linux-dev-8c7de408fd2caa11b8b3a1ed8a1b141ce127ad4e.zip
sis*: Move the Silicon Integrated Systems (SiS) drivers
Move the SiS drivers into drivers/net/ethernet/sis/ and make the necessary Kconfig and Makefile changes CC: Daniele Venzano <venza@brownhat.org> CC: Francois Romieu <romieu@fr.zoreil.com> Signed-off-by: Jeff Kirsher <jeffrey.t.kirsher@intel.com>
Diffstat (limited to 'drivers/net/ethernet/sis')
-rw-r--r--drivers/net/ethernet/sis/Kconfig50
-rw-r--r--drivers/net/ethernet/sis/Makefile6
-rw-r--r--drivers/net/ethernet/sis/sis190.c1956
-rw-r--r--drivers/net/ethernet/sis/sis900.c2494
-rw-r--r--drivers/net/ethernet/sis/sis900.h329
5 files changed, 4835 insertions, 0 deletions
diff --git a/drivers/net/ethernet/sis/Kconfig b/drivers/net/ethernet/sis/Kconfig
new file mode 100644
index 000000000000..01d43e870eeb
--- /dev/null
+++ b/drivers/net/ethernet/sis/Kconfig
@@ -0,0 +1,50 @@
+#
+# Silicon Integrated Systems (SiS) device configuration
+#
+
+config NET_VENDOR_SIS
+ bool "Silicon Integrated Systems (SiS) devices"
+ depends on PCI
+ ---help---
+ If you have a network (Ethernet) card belonging to this class, say Y
+ and read the Ethernet-HOWTO, available from
+ <http://www.tldp.org/docs.html#howto>.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about SiS devices. If you say Y, you will be asked for
+ your specific card in the following questions.
+
+if NET_VENDOR_SIS
+
+config SIS900
+ tristate "SiS 900/7016 PCI Fast Ethernet Adapter support"
+ depends on PCI
+ select CRC32
+ select MII
+ ---help---
+ This is a driver for the Fast Ethernet PCI network cards based on
+ the SiS 900 and SiS 7016 chips. The SiS 900 core is also embedded in
+ SiS 630 and SiS 540 chipsets.
+
+ This driver also supports AMD 79C901 HomePNA so that you can use
+ your phone line as a network cable.
+
+ To compile this driver as a module, choose M here: the module
+ will be called sis900. This is recommended.
+
+config SIS190
+ tristate "SiS190/SiS191 gigabit ethernet support"
+ depends on PCI
+ select CRC32
+ select MII
+ ---help---
+ Say Y here if you have a SiS 190 PCI Fast Ethernet adapter or
+ a SiS 191 PCI Gigabit Ethernet adapter. Both are expected to
+ appear in lan on motherboard designs which are based on SiS 965
+ and SiS 966 south bridge.
+
+ To compile this driver as a module, choose M here: the module
+ will be called sis190. This is recommended.
+
+endif # NET_VENDOR_SIS
diff --git a/drivers/net/ethernet/sis/Makefile b/drivers/net/ethernet/sis/Makefile
new file mode 100644
index 000000000000..58d3ac1985df
--- /dev/null
+++ b/drivers/net/ethernet/sis/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for Silicon Integrated Systems (SiS) network device drivers.
+#
+
+obj-$(CONFIG_SIS190) += sis190.o
+obj-$(CONFIG_SIS900) += sis900.o
diff --git a/drivers/net/ethernet/sis/sis190.c b/drivers/net/ethernet/sis/sis190.c
new file mode 100644
index 000000000000..3c0f1312b391
--- /dev/null
+++ b/drivers/net/ethernet/sis/sis190.c
@@ -0,0 +1,1956 @@
+/*
+ sis190.c: Silicon Integrated Systems SiS190 ethernet driver
+
+ Copyright (c) 2003 K.M. Liu <kmliu@sis.com>
+ Copyright (c) 2003, 2004 Jeff Garzik <jgarzik@pobox.com>
+ Copyright (c) 2003, 2004, 2005 Francois Romieu <romieu@fr.zoreil.com>
+
+ Based on r8169.c, tg3.c, 8139cp.c, skge.c, epic100.c and SiS 190/191
+ genuine driver.
+
+ This software may be used and distributed according to the terms of
+ the GNU General Public License (GPL), incorporated herein by reference.
+ Drivers based on or derived from this code fall under the GPL and must
+ retain the authorship, copyright and license notice. This file is not
+ a complete program and may only be used when the entire operating
+ system is licensed under the GPL.
+
+ See the file COPYING in this distribution for more information.
+
+*/
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/pci.h>
+#include <linux/mii.h>
+#include <linux/delay.h>
+#include <linux/crc32.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <asm/irq.h>
+
+#define PHY_MAX_ADDR 32
+#define PHY_ID_ANY 0x1f
+#define MII_REG_ANY 0x1f
+
+#define DRV_VERSION "1.4"
+#define DRV_NAME "sis190"
+#define SIS190_DRIVER_NAME DRV_NAME " Gigabit Ethernet driver " DRV_VERSION
+
+#define sis190_rx_skb netif_rx
+#define sis190_rx_quota(count, quota) count
+
+#define MAC_ADDR_LEN 6
+
+#define NUM_TX_DESC 64 /* [8..1024] */
+#define NUM_RX_DESC 64 /* [8..8192] */
+#define TX_RING_BYTES (NUM_TX_DESC * sizeof(struct TxDesc))
+#define RX_RING_BYTES (NUM_RX_DESC * sizeof(struct RxDesc))
+#define RX_BUF_SIZE 1536
+#define RX_BUF_MASK 0xfff8
+
+#define SIS190_REGS_SIZE 0x80
+#define SIS190_TX_TIMEOUT (6*HZ)
+#define SIS190_PHY_TIMEOUT (10*HZ)
+#define SIS190_MSG_DEFAULT (NETIF_MSG_DRV | NETIF_MSG_PROBE | \
+ NETIF_MSG_LINK | NETIF_MSG_IFUP | \
+ NETIF_MSG_IFDOWN)
+
+/* Enhanced PHY access register bit definitions */
+#define EhnMIIread 0x0000
+#define EhnMIIwrite 0x0020
+#define EhnMIIdataShift 16
+#define EhnMIIpmdShift 6 /* 7016 only */
+#define EhnMIIregShift 11
+#define EhnMIIreq 0x0010
+#define EhnMIInotDone 0x0010
+
+/* Write/read MMIO register */
+#define SIS_W8(reg, val) writeb ((val), ioaddr + (reg))
+#define SIS_W16(reg, val) writew ((val), ioaddr + (reg))
+#define SIS_W32(reg, val) writel ((val), ioaddr + (reg))
+#define SIS_R8(reg) readb (ioaddr + (reg))
+#define SIS_R16(reg) readw (ioaddr + (reg))
+#define SIS_R32(reg) readl (ioaddr + (reg))
+
+#define SIS_PCI_COMMIT() SIS_R32(IntrControl)
+
+enum sis190_registers {
+ TxControl = 0x00,
+ TxDescStartAddr = 0x04,
+ rsv0 = 0x08, // reserved
+ TxSts = 0x0c, // unused (Control/Status)
+ RxControl = 0x10,
+ RxDescStartAddr = 0x14,
+ rsv1 = 0x18, // reserved
+ RxSts = 0x1c, // unused
+ IntrStatus = 0x20,
+ IntrMask = 0x24,
+ IntrControl = 0x28,
+ IntrTimer = 0x2c, // unused (Interrupt Timer)
+ PMControl = 0x30, // unused (Power Mgmt Control/Status)
+ rsv2 = 0x34, // reserved
+ ROMControl = 0x38,
+ ROMInterface = 0x3c,
+ StationControl = 0x40,
+ GMIIControl = 0x44,
+ GIoCR = 0x48, // unused (GMAC IO Compensation)
+ GIoCtrl = 0x4c, // unused (GMAC IO Control)
+ TxMacControl = 0x50,
+ TxLimit = 0x54, // unused (Tx MAC Timer/TryLimit)
+ RGDelay = 0x58, // unused (RGMII Tx Internal Delay)
+ rsv3 = 0x5c, // reserved
+ RxMacControl = 0x60,
+ RxMacAddr = 0x62,
+ RxHashTable = 0x68,
+ // Undocumented = 0x6c,
+ RxWolCtrl = 0x70,
+ RxWolData = 0x74, // unused (Rx WOL Data Access)
+ RxMPSControl = 0x78, // unused (Rx MPS Control)
+ rsv4 = 0x7c, // reserved
+};
+
+enum sis190_register_content {
+ /* IntrStatus */
+ SoftInt = 0x40000000, // unused
+ Timeup = 0x20000000, // unused
+ PauseFrame = 0x00080000, // unused
+ MagicPacket = 0x00040000, // unused
+ WakeupFrame = 0x00020000, // unused
+ LinkChange = 0x00010000,
+ RxQEmpty = 0x00000080,
+ RxQInt = 0x00000040,
+ TxQ1Empty = 0x00000020, // unused
+ TxQ1Int = 0x00000010,
+ TxQ0Empty = 0x00000008, // unused
+ TxQ0Int = 0x00000004,
+ RxHalt = 0x00000002,
+ TxHalt = 0x00000001,
+
+ /* {Rx/Tx}CmdBits */
+ CmdReset = 0x10,
+ CmdRxEnb = 0x08, // unused
+ CmdTxEnb = 0x01,
+ RxBufEmpty = 0x01, // unused
+
+ /* Cfg9346Bits */
+ Cfg9346_Lock = 0x00, // unused
+ Cfg9346_Unlock = 0xc0, // unused
+
+ /* RxMacControl */
+ AcceptErr = 0x20, // unused
+ AcceptRunt = 0x10, // unused
+ AcceptBroadcast = 0x0800,
+ AcceptMulticast = 0x0400,
+ AcceptMyPhys = 0x0200,
+ AcceptAllPhys = 0x0100,
+
+ /* RxConfigBits */
+ RxCfgFIFOShift = 13,
+ RxCfgDMAShift = 8, // 0x1a in RxControl ?
+
+ /* TxConfigBits */
+ TxInterFrameGapShift = 24,
+ TxDMAShift = 8, /* DMA burst value (0-7) is shift this many bits */
+
+ LinkStatus = 0x02, // unused
+ FullDup = 0x01, // unused
+
+ /* TBICSRBit */
+ TBILinkOK = 0x02000000, // unused
+};
+
+struct TxDesc {
+ __le32 PSize;
+ __le32 status;
+ __le32 addr;
+ __le32 size;
+};
+
+struct RxDesc {
+ __le32 PSize;
+ __le32 status;
+ __le32 addr;
+ __le32 size;
+};
+
+enum _DescStatusBit {
+ /* _Desc.status */
+ OWNbit = 0x80000000, // RXOWN/TXOWN
+ INTbit = 0x40000000, // RXINT/TXINT
+ CRCbit = 0x00020000, // CRCOFF/CRCEN
+ PADbit = 0x00010000, // PREADD/PADEN
+ /* _Desc.size */
+ RingEnd = 0x80000000,
+ /* TxDesc.status */
+ LSEN = 0x08000000, // TSO ? -- FR
+ IPCS = 0x04000000,
+ TCPCS = 0x02000000,
+ UDPCS = 0x01000000,
+ BSTEN = 0x00800000,
+ EXTEN = 0x00400000,
+ DEFEN = 0x00200000,
+ BKFEN = 0x00100000,
+ CRSEN = 0x00080000,
+ COLEN = 0x00040000,
+ THOL3 = 0x30000000,
+ THOL2 = 0x20000000,
+ THOL1 = 0x10000000,
+ THOL0 = 0x00000000,
+
+ WND = 0x00080000,
+ TABRT = 0x00040000,
+ FIFO = 0x00020000,
+ LINK = 0x00010000,
+ ColCountMask = 0x0000ffff,
+ /* RxDesc.status */
+ IPON = 0x20000000,
+ TCPON = 0x10000000,
+ UDPON = 0x08000000,
+ Wakup = 0x00400000,
+ Magic = 0x00200000,
+ Pause = 0x00100000,
+ DEFbit = 0x00200000,
+ BCAST = 0x000c0000,
+ MCAST = 0x00080000,
+ UCAST = 0x00040000,
+ /* RxDesc.PSize */
+ TAGON = 0x80000000,
+ RxDescCountMask = 0x7f000000, // multi-desc pkt when > 1 ? -- FR
+ ABORT = 0x00800000,
+ SHORT = 0x00400000,
+ LIMIT = 0x00200000,
+ MIIER = 0x00100000,
+ OVRUN = 0x00080000,
+ NIBON = 0x00040000,
+ COLON = 0x00020000,
+ CRCOK = 0x00010000,
+ RxSizeMask = 0x0000ffff
+ /*
+ * The asic could apparently do vlan, TSO, jumbo (sis191 only) and
+ * provide two (unused with Linux) Tx queues. No publicly
+ * available documentation alas.
+ */
+};
+
+enum sis190_eeprom_access_register_bits {
+ EECS = 0x00000001, // unused
+ EECLK = 0x00000002, // unused
+ EEDO = 0x00000008, // unused
+ EEDI = 0x00000004, // unused
+ EEREQ = 0x00000080,
+ EEROP = 0x00000200,
+ EEWOP = 0x00000100 // unused
+};
+
+/* EEPROM Addresses */
+enum sis190_eeprom_address {
+ EEPROMSignature = 0x00,
+ EEPROMCLK = 0x01, // unused
+ EEPROMInfo = 0x02,
+ EEPROMMACAddr = 0x03
+};
+
+enum sis190_feature {
+ F_HAS_RGMII = 1,
+ F_PHY_88E1111 = 2,
+ F_PHY_BCM5461 = 4
+};
+
+struct sis190_private {
+ void __iomem *mmio_addr;
+ struct pci_dev *pci_dev;
+ struct net_device *dev;
+ spinlock_t lock;
+ u32 rx_buf_sz;
+ u32 cur_rx;
+ u32 cur_tx;
+ u32 dirty_rx;
+ u32 dirty_tx;
+ dma_addr_t rx_dma;
+ dma_addr_t tx_dma;
+ struct RxDesc *RxDescRing;
+ struct TxDesc *TxDescRing;
+ struct sk_buff *Rx_skbuff[NUM_RX_DESC];
+ struct sk_buff *Tx_skbuff[NUM_TX_DESC];
+ struct work_struct phy_task;
+ struct timer_list timer;
+ u32 msg_enable;
+ struct mii_if_info mii_if;
+ struct list_head first_phy;
+ u32 features;
+ u32 negotiated_lpa;
+ enum {
+ LNK_OFF,
+ LNK_ON,
+ LNK_AUTONEG,
+ } link_status;
+};
+
+struct sis190_phy {
+ struct list_head list;
+ int phy_id;
+ u16 id[2];
+ u16 status;
+ u8 type;
+};
+
+enum sis190_phy_type {
+ UNKNOWN = 0x00,
+ HOME = 0x01,
+ LAN = 0x02,
+ MIX = 0x03
+};
+
+static struct mii_chip_info {
+ const char *name;
+ u16 id[2];
+ unsigned int type;
+ u32 feature;
+} mii_chip_table[] = {
+ { "Atheros PHY", { 0x004d, 0xd010 }, LAN, 0 },
+ { "Atheros PHY AR8012", { 0x004d, 0xd020 }, LAN, 0 },
+ { "Broadcom PHY BCM5461", { 0x0020, 0x60c0 }, LAN, F_PHY_BCM5461 },
+ { "Broadcom PHY AC131", { 0x0143, 0xbc70 }, LAN, 0 },
+ { "Agere PHY ET1101B", { 0x0282, 0xf010 }, LAN, 0 },
+ { "Marvell PHY 88E1111", { 0x0141, 0x0cc0 }, LAN, F_PHY_88E1111 },
+ { "Realtek PHY RTL8201", { 0x0000, 0x8200 }, LAN, 0 },
+ { NULL, }
+};
+
+static const struct {
+ const char *name;
+} sis_chip_info[] = {
+ { "SiS 190 PCI Fast Ethernet adapter" },
+ { "SiS 191 PCI Gigabit Ethernet adapter" },
+};
+
+static DEFINE_PCI_DEVICE_TABLE(sis190_pci_tbl) = {
+ { PCI_DEVICE(PCI_VENDOR_ID_SI, 0x0190), 0, 0, 0 },
+ { PCI_DEVICE(PCI_VENDOR_ID_SI, 0x0191), 0, 0, 1 },
+ { 0, },
+};
+
+MODULE_DEVICE_TABLE(pci, sis190_pci_tbl);
+
+static int rx_copybreak = 200;
+
+static struct {
+ u32 msg_enable;
+} debug = { -1 };
+
+MODULE_DESCRIPTION("SiS sis190/191 Gigabit Ethernet driver");
+module_param(rx_copybreak, int, 0);
+MODULE_PARM_DESC(rx_copybreak, "Copy breakpoint for copy-only-tiny-frames");
+module_param_named(debug, debug.msg_enable, int, 0);
+MODULE_PARM_DESC(debug, "Debug verbosity level (0=none, ..., 16=all)");
+MODULE_AUTHOR("K.M. Liu <kmliu@sis.com>, Ueimor <romieu@fr.zoreil.com>");
+MODULE_VERSION(DRV_VERSION);
+MODULE_LICENSE("GPL");
+
+static const u32 sis190_intr_mask =
+ RxQEmpty | RxQInt | TxQ1Int | TxQ0Int | RxHalt | TxHalt | LinkChange;
+
+/*
+ * Maximum number of multicast addresses to filter (vs. Rx-all-multicast).
+ * The chips use a 64 element hash table based on the Ethernet CRC.
+ */
+static const int multicast_filter_limit = 32;
+
+static void __mdio_cmd(void __iomem *ioaddr, u32 ctl)
+{
+ unsigned int i;
+
+ SIS_W32(GMIIControl, ctl);
+
+ msleep(1);
+
+ for (i = 0; i < 100; i++) {
+ if (!(SIS_R32(GMIIControl) & EhnMIInotDone))
+ break;
+ msleep(1);
+ }
+
+ if (i > 99)
+ pr_err("PHY command failed !\n");
+}
+
+static void mdio_write(void __iomem *ioaddr, int phy_id, int reg, int val)
+{
+ __mdio_cmd(ioaddr, EhnMIIreq | EhnMIIwrite |
+ (((u32) reg) << EhnMIIregShift) | (phy_id << EhnMIIpmdShift) |
+ (((u32) val) << EhnMIIdataShift));
+}
+
+static int mdio_read(void __iomem *ioaddr, int phy_id, int reg)
+{
+ __mdio_cmd(ioaddr, EhnMIIreq | EhnMIIread |
+ (((u32) reg) << EhnMIIregShift) | (phy_id << EhnMIIpmdShift));
+
+ return (u16) (SIS_R32(GMIIControl) >> EhnMIIdataShift);
+}
+
+static void __mdio_write(struct net_device *dev, int phy_id, int reg, int val)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ mdio_write(tp->mmio_addr, phy_id, reg, val);
+}
+
+static int __mdio_read(struct net_device *dev, int phy_id, int reg)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ return mdio_read(tp->mmio_addr, phy_id, reg);
+}
+
+static u16 mdio_read_latched(void __iomem *ioaddr, int phy_id, int reg)
+{
+ mdio_read(ioaddr, phy_id, reg);
+ return mdio_read(ioaddr, phy_id, reg);
+}
+
+static u16 __devinit sis190_read_eeprom(void __iomem *ioaddr, u32 reg)
+{
+ u16 data = 0xffff;
+ unsigned int i;
+
+ if (!(SIS_R32(ROMControl) & 0x0002))
+ return 0;
+
+ SIS_W32(ROMInterface, EEREQ | EEROP | (reg << 10));
+
+ for (i = 0; i < 200; i++) {
+ if (!(SIS_R32(ROMInterface) & EEREQ)) {
+ data = (SIS_R32(ROMInterface) & 0xffff0000) >> 16;
+ break;
+ }
+ msleep(1);
+ }
+
+ return data;
+}
+
+static void sis190_irq_mask_and_ack(void __iomem *ioaddr)
+{
+ SIS_W32(IntrMask, 0x00);
+ SIS_W32(IntrStatus, 0xffffffff);
+ SIS_PCI_COMMIT();
+}
+
+static void sis190_asic_down(void __iomem *ioaddr)
+{
+ /* Stop the chip's Tx and Rx DMA processes. */
+
+ SIS_W32(TxControl, 0x1a00);
+ SIS_W32(RxControl, 0x1a00);
+
+ sis190_irq_mask_and_ack(ioaddr);
+}
+
+static void sis190_mark_as_last_descriptor(struct RxDesc *desc)
+{
+ desc->size |= cpu_to_le32(RingEnd);
+}
+
+static inline void sis190_give_to_asic(struct RxDesc *desc, u32 rx_buf_sz)
+{
+ u32 eor = le32_to_cpu(desc->size) & RingEnd;
+
+ desc->PSize = 0x0;
+ desc->size = cpu_to_le32((rx_buf_sz & RX_BUF_MASK) | eor);
+ wmb();
+ desc->status = cpu_to_le32(OWNbit | INTbit);
+}
+
+static inline void sis190_map_to_asic(struct RxDesc *desc, dma_addr_t mapping,
+ u32 rx_buf_sz)
+{
+ desc->addr = cpu_to_le32(mapping);
+ sis190_give_to_asic(desc, rx_buf_sz);
+}
+
+static inline void sis190_make_unusable_by_asic(struct RxDesc *desc)
+{
+ desc->PSize = 0x0;
+ desc->addr = cpu_to_le32(0xdeadbeef);
+ desc->size &= cpu_to_le32(RingEnd);
+ wmb();
+ desc->status = 0x0;
+}
+
+static struct sk_buff *sis190_alloc_rx_skb(struct sis190_private *tp,
+ struct RxDesc *desc)
+{
+ u32 rx_buf_sz = tp->rx_buf_sz;
+ struct sk_buff *skb;
+ dma_addr_t mapping;
+
+ skb = netdev_alloc_skb(tp->dev, rx_buf_sz);
+ if (unlikely(!skb))
+ goto skb_alloc_failed;
+ mapping = pci_map_single(tp->pci_dev, skb->data, tp->rx_buf_sz,
+ PCI_DMA_FROMDEVICE);
+ if (pci_dma_mapping_error(tp->pci_dev, mapping))
+ goto out;
+ sis190_map_to_asic(desc, mapping, rx_buf_sz);
+
+ return skb;
+
+out:
+ dev_kfree_skb_any(skb);
+skb_alloc_failed:
+ sis190_make_unusable_by_asic(desc);
+ return NULL;
+}
+
+static u32 sis190_rx_fill(struct sis190_private *tp, struct net_device *dev,
+ u32 start, u32 end)
+{
+ u32 cur;
+
+ for (cur = start; cur < end; cur++) {
+ unsigned int i = cur % NUM_RX_DESC;
+
+ if (tp->Rx_skbuff[i])
+ continue;
+
+ tp->Rx_skbuff[i] = sis190_alloc_rx_skb(tp, tp->RxDescRing + i);
+
+ if (!tp->Rx_skbuff[i])
+ break;
+ }
+ return cur - start;
+}
+
+static bool sis190_try_rx_copy(struct sis190_private *tp,
+ struct sk_buff **sk_buff, int pkt_size,
+ dma_addr_t addr)
+{
+ struct sk_buff *skb;
+ bool done = false;
+
+ if (pkt_size >= rx_copybreak)
+ goto out;
+
+ skb = netdev_alloc_skb_ip_align(tp->dev, pkt_size);
+ if (!skb)
+ goto out;
+
+ pci_dma_sync_single_for_cpu(tp->pci_dev, addr, tp->rx_buf_sz,
+ PCI_DMA_FROMDEVICE);
+ skb_copy_to_linear_data(skb, sk_buff[0]->data, pkt_size);
+ *sk_buff = skb;
+ done = true;
+out:
+ return done;
+}
+
+static inline int sis190_rx_pkt_err(u32 status, struct net_device_stats *stats)
+{
+#define ErrMask (OVRUN | SHORT | LIMIT | MIIER | NIBON | COLON | ABORT)
+
+ if ((status & CRCOK) && !(status & ErrMask))
+ return 0;
+
+ if (!(status & CRCOK))
+ stats->rx_crc_errors++;
+ else if (status & OVRUN)
+ stats->rx_over_errors++;
+ else if (status & (SHORT | LIMIT))
+ stats->rx_length_errors++;
+ else if (status & (MIIER | NIBON | COLON))
+ stats->rx_frame_errors++;
+
+ stats->rx_errors++;
+ return -1;
+}
+
+static int sis190_rx_interrupt(struct net_device *dev,
+ struct sis190_private *tp, void __iomem *ioaddr)
+{
+ struct net_device_stats *stats = &dev->stats;
+ u32 rx_left, cur_rx = tp->cur_rx;
+ u32 delta, count;
+
+ rx_left = NUM_RX_DESC + tp->dirty_rx - cur_rx;
+ rx_left = sis190_rx_quota(rx_left, (u32) dev->quota);
+
+ for (; rx_left > 0; rx_left--, cur_rx++) {
+ unsigned int entry = cur_rx % NUM_RX_DESC;
+ struct RxDesc *desc = tp->RxDescRing + entry;
+ u32 status;
+
+ if (le32_to_cpu(desc->status) & OWNbit)
+ break;
+
+ status = le32_to_cpu(desc->PSize);
+
+ //netif_info(tp, intr, dev, "Rx PSize = %08x\n", status);
+
+ if (sis190_rx_pkt_err(status, stats) < 0)
+ sis190_give_to_asic(desc, tp->rx_buf_sz);
+ else {
+ struct sk_buff *skb = tp->Rx_skbuff[entry];
+ dma_addr_t addr = le32_to_cpu(desc->addr);
+ int pkt_size = (status & RxSizeMask) - 4;
+ struct pci_dev *pdev = tp->pci_dev;
+
+ if (unlikely(pkt_size > tp->rx_buf_sz)) {
+ netif_info(tp, intr, dev,
+ "(frag) status = %08x\n", status);
+ stats->rx_dropped++;
+ stats->rx_length_errors++;
+ sis190_give_to_asic(desc, tp->rx_buf_sz);
+ continue;
+ }
+
+
+ if (sis190_try_rx_copy(tp, &skb, pkt_size, addr)) {
+ pci_dma_sync_single_for_device(pdev, addr,
+ tp->rx_buf_sz, PCI_DMA_FROMDEVICE);
+ sis190_give_to_asic(desc, tp->rx_buf_sz);
+ } else {
+ pci_unmap_single(pdev, addr, tp->rx_buf_sz,
+ PCI_DMA_FROMDEVICE);
+ tp->Rx_skbuff[entry] = NULL;
+ sis190_make_unusable_by_asic(desc);
+ }
+
+ skb_put(skb, pkt_size);
+ skb->protocol = eth_type_trans(skb, dev);
+
+ sis190_rx_skb(skb);
+
+ stats->rx_packets++;
+ stats->rx_bytes += pkt_size;
+ if ((status & BCAST) == MCAST)
+ stats->multicast++;
+ }
+ }
+ count = cur_rx - tp->cur_rx;
+ tp->cur_rx = cur_rx;
+
+ delta = sis190_rx_fill(tp, dev, tp->dirty_rx, tp->cur_rx);
+ if (!delta && count)
+ netif_info(tp, intr, dev, "no Rx buffer allocated\n");
+ tp->dirty_rx += delta;
+
+ if ((tp->dirty_rx + NUM_RX_DESC) == tp->cur_rx)
+ netif_emerg(tp, intr, dev, "Rx buffers exhausted\n");
+
+ return count;
+}
+
+static void sis190_unmap_tx_skb(struct pci_dev *pdev, struct sk_buff *skb,
+ struct TxDesc *desc)
+{
+ unsigned int len;
+
+ len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len;
+
+ pci_unmap_single(pdev, le32_to_cpu(desc->addr), len, PCI_DMA_TODEVICE);
+
+ memset(desc, 0x00, sizeof(*desc));
+}
+
+static inline int sis190_tx_pkt_err(u32 status, struct net_device_stats *stats)
+{
+#define TxErrMask (WND | TABRT | FIFO | LINK)
+
+ if (!unlikely(status & TxErrMask))
+ return 0;
+
+ if (status & WND)
+ stats->tx_window_errors++;
+ if (status & TABRT)
+ stats->tx_aborted_errors++;
+ if (status & FIFO)
+ stats->tx_fifo_errors++;
+ if (status & LINK)
+ stats->tx_carrier_errors++;
+
+ stats->tx_errors++;
+
+ return -1;
+}
+
+static void sis190_tx_interrupt(struct net_device *dev,
+ struct sis190_private *tp, void __iomem *ioaddr)
+{
+ struct net_device_stats *stats = &dev->stats;
+ u32 pending, dirty_tx = tp->dirty_tx;
+ /*
+ * It would not be needed if queueing was allowed to be enabled
+ * again too early (hint: think preempt and unclocked smp systems).
+ */
+ unsigned int queue_stopped;
+
+ smp_rmb();
+ pending = tp->cur_tx - dirty_tx;
+ queue_stopped = (pending == NUM_TX_DESC);
+
+ for (; pending; pending--, dirty_tx++) {
+ unsigned int entry = dirty_tx % NUM_TX_DESC;
+ struct TxDesc *txd = tp->TxDescRing + entry;
+ u32 status = le32_to_cpu(txd->status);
+ struct sk_buff *skb;
+
+ if (status & OWNbit)
+ break;
+
+ skb = tp->Tx_skbuff[entry];
+
+ if (likely(sis190_tx_pkt_err(status, stats) == 0)) {
+ stats->tx_packets++;
+ stats->tx_bytes += skb->len;
+ stats->collisions += ((status & ColCountMask) - 1);
+ }
+
+ sis190_unmap_tx_skb(tp->pci_dev, skb, txd);
+ tp->Tx_skbuff[entry] = NULL;
+ dev_kfree_skb_irq(skb);
+ }
+
+ if (tp->dirty_tx != dirty_tx) {
+ tp->dirty_tx = dirty_tx;
+ smp_wmb();
+ if (queue_stopped)
+ netif_wake_queue(dev);
+ }
+}
+
+/*
+ * The interrupt handler does all of the Rx thread work and cleans up after
+ * the Tx thread.
+ */
+static irqreturn_t sis190_interrupt(int irq, void *__dev)
+{
+ struct net_device *dev = __dev;
+ struct sis190_private *tp = netdev_priv(dev);
+ void __iomem *ioaddr = tp->mmio_addr;
+ unsigned int handled = 0;
+ u32 status;
+
+ status = SIS_R32(IntrStatus);
+
+ if ((status == 0xffffffff) || !status)
+ goto out;
+
+ handled = 1;
+
+ if (unlikely(!netif_running(dev))) {
+ sis190_asic_down(ioaddr);
+ goto out;
+ }
+
+ SIS_W32(IntrStatus, status);
+
+// netif_info(tp, intr, dev, "status = %08x\n", status);
+
+ if (status & LinkChange) {
+ netif_info(tp, intr, dev, "link change\n");
+ del_timer(&tp->timer);
+ schedule_work(&tp->phy_task);
+ }
+
+ if (status & RxQInt)
+ sis190_rx_interrupt(dev, tp, ioaddr);
+
+ if (status & TxQ0Int)
+ sis190_tx_interrupt(dev, tp, ioaddr);
+out:
+ return IRQ_RETVAL(handled);
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+static void sis190_netpoll(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ struct pci_dev *pdev = tp->pci_dev;
+
+ disable_irq(pdev->irq);
+ sis190_interrupt(pdev->irq, dev);
+ enable_irq(pdev->irq);
+}
+#endif
+
+static void sis190_free_rx_skb(struct sis190_private *tp,
+ struct sk_buff **sk_buff, struct RxDesc *desc)
+{
+ struct pci_dev *pdev = tp->pci_dev;
+
+ pci_unmap_single(pdev, le32_to_cpu(desc->addr), tp->rx_buf_sz,
+ PCI_DMA_FROMDEVICE);
+ dev_kfree_skb(*sk_buff);
+ *sk_buff = NULL;
+ sis190_make_unusable_by_asic(desc);
+}
+
+static void sis190_rx_clear(struct sis190_private *tp)
+{
+ unsigned int i;
+
+ for (i = 0; i < NUM_RX_DESC; i++) {
+ if (!tp->Rx_skbuff[i])
+ continue;
+ sis190_free_rx_skb(tp, tp->Rx_skbuff + i, tp->RxDescRing + i);
+ }
+}
+
+static void sis190_init_ring_indexes(struct sis190_private *tp)
+{
+ tp->dirty_tx = tp->dirty_rx = tp->cur_tx = tp->cur_rx = 0;
+}
+
+static int sis190_init_ring(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ sis190_init_ring_indexes(tp);
+
+ memset(tp->Tx_skbuff, 0x0, NUM_TX_DESC * sizeof(struct sk_buff *));
+ memset(tp->Rx_skbuff, 0x0, NUM_RX_DESC * sizeof(struct sk_buff *));
+
+ if (sis190_rx_fill(tp, dev, 0, NUM_RX_DESC) != NUM_RX_DESC)
+ goto err_rx_clear;
+
+ sis190_mark_as_last_descriptor(tp->RxDescRing + NUM_RX_DESC - 1);
+
+ return 0;
+
+err_rx_clear:
+ sis190_rx_clear(tp);
+ return -ENOMEM;
+}
+
+static void sis190_set_rx_mode(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ void __iomem *ioaddr = tp->mmio_addr;
+ unsigned long flags;
+ u32 mc_filter[2]; /* Multicast hash filter */
+ u16 rx_mode;
+
+ if (dev->flags & IFF_PROMISC) {
+ rx_mode =
+ AcceptBroadcast | AcceptMulticast | AcceptMyPhys |
+ AcceptAllPhys;
+ mc_filter[1] = mc_filter[0] = 0xffffffff;
+ } else if ((netdev_mc_count(dev) > multicast_filter_limit) ||
+ (dev->flags & IFF_ALLMULTI)) {
+ /* Too many to filter perfectly -- accept all multicasts. */
+ rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys;
+ mc_filter[1] = mc_filter[0] = 0xffffffff;
+ } else {
+ struct netdev_hw_addr *ha;
+
+ rx_mode = AcceptBroadcast | AcceptMyPhys;
+ mc_filter[1] = mc_filter[0] = 0;
+ netdev_for_each_mc_addr(ha, dev) {
+ int bit_nr =
+ ether_crc(ETH_ALEN, ha->addr) & 0x3f;
+ mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31);
+ rx_mode |= AcceptMulticast;
+ }
+ }
+
+ spin_lock_irqsave(&tp->lock, flags);
+
+ SIS_W16(RxMacControl, rx_mode | 0x2);
+ SIS_W32(RxHashTable, mc_filter[0]);
+ SIS_W32(RxHashTable + 4, mc_filter[1]);
+
+ spin_unlock_irqrestore(&tp->lock, flags);
+}
+
+static void sis190_soft_reset(void __iomem *ioaddr)
+{
+ SIS_W32(IntrControl, 0x8000);
+ SIS_PCI_COMMIT();
+ SIS_W32(IntrControl, 0x0);
+ sis190_asic_down(ioaddr);
+}
+
+static void sis190_hw_start(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ void __iomem *ioaddr = tp->mmio_addr;
+
+ sis190_soft_reset(ioaddr);
+
+ SIS_W32(TxDescStartAddr, tp->tx_dma);
+ SIS_W32(RxDescStartAddr, tp->rx_dma);
+
+ SIS_W32(IntrStatus, 0xffffffff);
+ SIS_W32(IntrMask, 0x0);
+ SIS_W32(GMIIControl, 0x0);
+ SIS_W32(TxMacControl, 0x60);
+ SIS_W16(RxMacControl, 0x02);
+ SIS_W32(RxHashTable, 0x0);
+ SIS_W32(0x6c, 0x0);
+ SIS_W32(RxWolCtrl, 0x0);
+ SIS_W32(RxWolData, 0x0);
+
+ SIS_PCI_COMMIT();
+
+ sis190_set_rx_mode(dev);
+
+ /* Enable all known interrupts by setting the interrupt mask. */
+ SIS_W32(IntrMask, sis190_intr_mask);
+
+ SIS_W32(TxControl, 0x1a00 | CmdTxEnb);
+ SIS_W32(RxControl, 0x1a1d);
+
+ netif_start_queue(dev);
+}
+
+static void sis190_phy_task(struct work_struct *work)
+{
+ struct sis190_private *tp =
+ container_of(work, struct sis190_private, phy_task);
+ struct net_device *dev = tp->dev;
+ void __iomem *ioaddr = tp->mmio_addr;
+ int phy_id = tp->mii_if.phy_id;
+ u16 val;
+
+ rtnl_lock();
+
+ if (!netif_running(dev))
+ goto out_unlock;
+
+ val = mdio_read(ioaddr, phy_id, MII_BMCR);
+ if (val & BMCR_RESET) {
+ // FIXME: needlessly high ? -- FR 02/07/2005
+ mod_timer(&tp->timer, jiffies + HZ/10);
+ goto out_unlock;
+ }
+
+ val = mdio_read_latched(ioaddr, phy_id, MII_BMSR);
+ if (!(val & BMSR_ANEGCOMPLETE) && tp->link_status != LNK_AUTONEG) {
+ netif_carrier_off(dev);
+ netif_warn(tp, link, dev, "auto-negotiating...\n");
+ tp->link_status = LNK_AUTONEG;
+ } else if ((val & BMSR_LSTATUS) && tp->link_status != LNK_ON) {
+ /* Rejoice ! */
+ struct {
+ int val;
+ u32 ctl;
+ const char *msg;
+ } reg31[] = {
+ { LPA_1000FULL, 0x07000c00 | 0x00001000,
+ "1000 Mbps Full Duplex" },
+ { LPA_1000HALF, 0x07000c00,
+ "1000 Mbps Half Duplex" },
+ { LPA_100FULL, 0x04000800 | 0x00001000,
+ "100 Mbps Full Duplex" },
+ { LPA_100HALF, 0x04000800,
+ "100 Mbps Half Duplex" },
+ { LPA_10FULL, 0x04000400 | 0x00001000,
+ "10 Mbps Full Duplex" },
+ { LPA_10HALF, 0x04000400,
+ "10 Mbps Half Duplex" },
+ { 0, 0x04000400, "unknown" }
+ }, *p = NULL;
+ u16 adv, autoexp, gigadv, gigrec;
+
+ val = mdio_read(ioaddr, phy_id, 0x1f);
+ netif_info(tp, link, dev, "mii ext = %04x\n", val);
+
+ val = mdio_read(ioaddr, phy_id, MII_LPA);
+ adv = mdio_read(ioaddr, phy_id, MII_ADVERTISE);
+ autoexp = mdio_read(ioaddr, phy_id, MII_EXPANSION);
+ netif_info(tp, link, dev, "mii lpa=%04x adv=%04x exp=%04x\n",
+ val, adv, autoexp);
+
+ if (val & LPA_NPAGE && autoexp & EXPANSION_NWAY) {
+ /* check for gigabit speed */
+ gigadv = mdio_read(ioaddr, phy_id, MII_CTRL1000);
+ gigrec = mdio_read(ioaddr, phy_id, MII_STAT1000);
+ val = (gigadv & (gigrec >> 2));
+ if (val & ADVERTISE_1000FULL)
+ p = reg31;
+ else if (val & ADVERTISE_1000HALF)
+ p = reg31 + 1;
+ }
+ if (!p) {
+ val &= adv;
+
+ for (p = reg31; p->val; p++) {
+ if ((val & p->val) == p->val)
+ break;
+ }
+ }
+
+ p->ctl |= SIS_R32(StationControl) & ~0x0f001c00;
+
+ if ((tp->features & F_HAS_RGMII) &&
+ (tp->features & F_PHY_BCM5461)) {
+ // Set Tx Delay in RGMII mode.
+ mdio_write(ioaddr, phy_id, 0x18, 0xf1c7);
+ udelay(200);
+ mdio_write(ioaddr, phy_id, 0x1c, 0x8c00);
+ p->ctl |= 0x03000000;
+ }
+
+ SIS_W32(StationControl, p->ctl);
+
+ if (tp->features & F_HAS_RGMII) {
+ SIS_W32(RGDelay, 0x0441);
+ SIS_W32(RGDelay, 0x0440);
+ }
+
+ tp->negotiated_lpa = p->val;
+
+ netif_info(tp, link, dev, "link on %s mode\n", p->msg);
+ netif_carrier_on(dev);
+ tp->link_status = LNK_ON;
+ } else if (!(val & BMSR_LSTATUS) && tp->link_status != LNK_AUTONEG)
+ tp->link_status = LNK_OFF;
+ mod_timer(&tp->timer, jiffies + SIS190_PHY_TIMEOUT);
+
+out_unlock:
+ rtnl_unlock();
+}
+
+static void sis190_phy_timer(unsigned long __opaque)
+{
+ struct net_device *dev = (struct net_device *)__opaque;
+ struct sis190_private *tp = netdev_priv(dev);
+
+ if (likely(netif_running(dev)))
+ schedule_work(&tp->phy_task);
+}
+
+static inline void sis190_delete_timer(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ del_timer_sync(&tp->timer);
+}
+
+static inline void sis190_request_timer(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ struct timer_list *timer = &tp->timer;
+
+ init_timer(timer);
+ timer->expires = jiffies + SIS190_PHY_TIMEOUT;
+ timer->data = (unsigned long)dev;
+ timer->function = sis190_phy_timer;
+ add_timer(timer);
+}
+
+static void sis190_set_rxbufsize(struct sis190_private *tp,
+ struct net_device *dev)
+{
+ unsigned int mtu = dev->mtu;
+
+ tp->rx_buf_sz = (mtu > RX_BUF_SIZE) ? mtu + ETH_HLEN + 8 : RX_BUF_SIZE;
+ /* RxDesc->size has a licence to kill the lower bits */
+ if (tp->rx_buf_sz & 0x07) {
+ tp->rx_buf_sz += 8;
+ tp->rx_buf_sz &= RX_BUF_MASK;
+ }
+}
+
+static int sis190_open(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ struct pci_dev *pdev = tp->pci_dev;
+ int rc = -ENOMEM;
+
+ sis190_set_rxbufsize(tp, dev);
+
+ /*
+ * Rx and Tx descriptors need 256 bytes alignment.
+ * pci_alloc_consistent() guarantees a stronger alignment.
+ */
+ tp->TxDescRing = pci_alloc_consistent(pdev, TX_RING_BYTES, &tp->tx_dma);
+ if (!tp->TxDescRing)
+ goto out;
+
+ tp->RxDescRing = pci_alloc_consistent(pdev, RX_RING_BYTES, &tp->rx_dma);
+ if (!tp->RxDescRing)
+ goto err_free_tx_0;
+
+ rc = sis190_init_ring(dev);
+ if (rc < 0)
+ goto err_free_rx_1;
+
+ sis190_request_timer(dev);
+
+ rc = request_irq(dev->irq, sis190_interrupt, IRQF_SHARED, dev->name, dev);
+ if (rc < 0)
+ goto err_release_timer_2;
+
+ sis190_hw_start(dev);
+out:
+ return rc;
+
+err_release_timer_2:
+ sis190_delete_timer(dev);
+ sis190_rx_clear(tp);
+err_free_rx_1:
+ pci_free_consistent(tp->pci_dev, RX_RING_BYTES, tp->RxDescRing,
+ tp->rx_dma);
+err_free_tx_0:
+ pci_free_consistent(tp->pci_dev, TX_RING_BYTES, tp->TxDescRing,
+ tp->tx_dma);
+ goto out;
+}
+
+static void sis190_tx_clear(struct sis190_private *tp)
+{
+ unsigned int i;
+
+ for (i = 0; i < NUM_TX_DESC; i++) {
+ struct sk_buff *skb = tp->Tx_skbuff[i];
+
+ if (!skb)
+ continue;
+
+ sis190_unmap_tx_skb(tp->pci_dev, skb, tp->TxDescRing + i);
+ tp->Tx_skbuff[i] = NULL;
+ dev_kfree_skb(skb);
+
+ tp->dev->stats.tx_dropped++;
+ }
+ tp->cur_tx = tp->dirty_tx = 0;
+}
+
+static void sis190_down(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ void __iomem *ioaddr = tp->mmio_addr;
+ unsigned int poll_locked = 0;
+
+ sis190_delete_timer(dev);
+
+ netif_stop_queue(dev);
+
+ do {
+ spin_lock_irq(&tp->lock);
+
+ sis190_asic_down(ioaddr);
+
+ spin_unlock_irq(&tp->lock);
+
+ synchronize_irq(dev->irq);
+
+ if (!poll_locked)
+ poll_locked++;
+
+ synchronize_sched();
+
+ } while (SIS_R32(IntrMask));
+
+ sis190_tx_clear(tp);
+ sis190_rx_clear(tp);
+}
+
+static int sis190_close(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ struct pci_dev *pdev = tp->pci_dev;
+
+ sis190_down(dev);
+
+ free_irq(dev->irq, dev);
+
+ pci_free_consistent(pdev, TX_RING_BYTES, tp->TxDescRing, tp->tx_dma);
+ pci_free_consistent(pdev, RX_RING_BYTES, tp->RxDescRing, tp->rx_dma);
+
+ tp->TxDescRing = NULL;
+ tp->RxDescRing = NULL;
+
+ return 0;
+}
+
+static netdev_tx_t sis190_start_xmit(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ void __iomem *ioaddr = tp->mmio_addr;
+ u32 len, entry, dirty_tx;
+ struct TxDesc *desc;
+ dma_addr_t mapping;
+
+ if (unlikely(skb->len < ETH_ZLEN)) {
+ if (skb_padto(skb, ETH_ZLEN)) {
+ dev->stats.tx_dropped++;
+ goto out;
+ }
+ len = ETH_ZLEN;
+ } else {
+ len = skb->len;
+ }
+
+ entry = tp->cur_tx % NUM_TX_DESC;
+ desc = tp->TxDescRing + entry;
+
+ if (unlikely(le32_to_cpu(desc->status) & OWNbit)) {
+ netif_stop_queue(dev);
+ netif_err(tp, tx_err, dev,
+ "BUG! Tx Ring full when queue awake!\n");
+ return NETDEV_TX_BUSY;
+ }
+
+ mapping = pci_map_single(tp->pci_dev, skb->data, len, PCI_DMA_TODEVICE);
+ if (pci_dma_mapping_error(tp->pci_dev, mapping)) {
+ netif_err(tp, tx_err, dev,
+ "PCI mapping failed, dropping packet");
+ return NETDEV_TX_BUSY;
+ }
+
+ tp->Tx_skbuff[entry] = skb;
+
+ desc->PSize = cpu_to_le32(len);
+ desc->addr = cpu_to_le32(mapping);
+
+ desc->size = cpu_to_le32(len);
+ if (entry == (NUM_TX_DESC - 1))
+ desc->size |= cpu_to_le32(RingEnd);
+
+ wmb();
+
+ desc->status = cpu_to_le32(OWNbit | INTbit | DEFbit | CRCbit | PADbit);
+ if (tp->negotiated_lpa & (LPA_1000HALF | LPA_100HALF | LPA_10HALF)) {
+ /* Half Duplex */
+ desc->status |= cpu_to_le32(COLEN | CRSEN | BKFEN);
+ if (tp->negotiated_lpa & (LPA_1000HALF | LPA_1000FULL))
+ desc->status |= cpu_to_le32(EXTEN | BSTEN); /* gigabit HD */
+ }
+
+ tp->cur_tx++;
+
+ smp_wmb();
+
+ SIS_W32(TxControl, 0x1a00 | CmdReset | CmdTxEnb);
+
+ dirty_tx = tp->dirty_tx;
+ if ((tp->cur_tx - NUM_TX_DESC) == dirty_tx) {
+ netif_stop_queue(dev);
+ smp_rmb();
+ if (dirty_tx != tp->dirty_tx)
+ netif_wake_queue(dev);
+ }
+out:
+ return NETDEV_TX_OK;
+}
+
+static void sis190_free_phy(struct list_head *first_phy)
+{
+ struct sis190_phy *cur, *next;
+
+ list_for_each_entry_safe(cur, next, first_phy, list) {
+ kfree(cur);
+ }
+}
+
+/**
+ * sis190_default_phy - Select default PHY for sis190 mac.
+ * @dev: the net device to probe for
+ *
+ * Select first detected PHY with link as default.
+ * If no one is link on, select PHY whose types is HOME as default.
+ * If HOME doesn't exist, select LAN.
+ */
+static u16 sis190_default_phy(struct net_device *dev)
+{
+ struct sis190_phy *phy, *phy_home, *phy_default, *phy_lan;
+ struct sis190_private *tp = netdev_priv(dev);
+ struct mii_if_info *mii_if = &tp->mii_if;
+ void __iomem *ioaddr = tp->mmio_addr;
+ u16 status;
+
+ phy_home = phy_default = phy_lan = NULL;
+
+ list_for_each_entry(phy, &tp->first_phy, list) {
+ status = mdio_read_latched(ioaddr, phy->phy_id, MII_BMSR);
+
+ // Link ON & Not select default PHY & not ghost PHY.
+ if ((status & BMSR_LSTATUS) &&
+ !phy_default &&
+ (phy->type != UNKNOWN)) {
+ phy_default = phy;
+ } else {
+ status = mdio_read(ioaddr, phy->phy_id, MII_BMCR);
+ mdio_write(ioaddr, phy->phy_id, MII_BMCR,
+ status | BMCR_ANENABLE | BMCR_ISOLATE);
+ if (phy->type == HOME)
+ phy_home = phy;
+ else if (phy->type == LAN)
+ phy_lan = phy;
+ }
+ }
+
+ if (!phy_default) {
+ if (phy_home)
+ phy_default = phy_home;
+ else if (phy_lan)
+ phy_default = phy_lan;
+ else
+ phy_default = list_first_entry(&tp->first_phy,
+ struct sis190_phy, list);
+ }
+
+ if (mii_if->phy_id != phy_default->phy_id) {
+ mii_if->phy_id = phy_default->phy_id;
+ if (netif_msg_probe(tp))
+ pr_info("%s: Using transceiver at address %d as default\n",
+ pci_name(tp->pci_dev), mii_if->phy_id);
+ }
+
+ status = mdio_read(ioaddr, mii_if->phy_id, MII_BMCR);
+ status &= (~BMCR_ISOLATE);
+
+ mdio_write(ioaddr, mii_if->phy_id, MII_BMCR, status);
+ status = mdio_read_latched(ioaddr, mii_if->phy_id, MII_BMSR);
+
+ return status;
+}
+
+static void sis190_init_phy(struct net_device *dev, struct sis190_private *tp,
+ struct sis190_phy *phy, unsigned int phy_id,
+ u16 mii_status)
+{
+ void __iomem *ioaddr = tp->mmio_addr;
+ struct mii_chip_info *p;
+
+ INIT_LIST_HEAD(&phy->list);
+ phy->status = mii_status;
+ phy->phy_id = phy_id;
+
+ phy->id[0] = mdio_read(ioaddr, phy_id, MII_PHYSID1);
+ phy->id[1] = mdio_read(ioaddr, phy_id, MII_PHYSID2);
+
+ for (p = mii_chip_table; p->type; p++) {
+ if ((p->id[0] == phy->id[0]) &&
+ (p->id[1] == (phy->id[1] & 0xfff0))) {
+ break;
+ }
+ }
+
+ if (p->id[1]) {
+ phy->type = (p->type == MIX) ?
+ ((mii_status & (BMSR_100FULL | BMSR_100HALF)) ?
+ LAN : HOME) : p->type;
+ tp->features |= p->feature;
+ if (netif_msg_probe(tp))
+ pr_info("%s: %s transceiver at address %d\n",
+ pci_name(tp->pci_dev), p->name, phy_id);
+ } else {
+ phy->type = UNKNOWN;
+ if (netif_msg_probe(tp))
+ pr_info("%s: unknown PHY 0x%x:0x%x transceiver at address %d\n",
+ pci_name(tp->pci_dev),
+ phy->id[0], (phy->id[1] & 0xfff0), phy_id);
+ }
+}
+
+static void sis190_mii_probe_88e1111_fixup(struct sis190_private *tp)
+{
+ if (tp->features & F_PHY_88E1111) {
+ void __iomem *ioaddr = tp->mmio_addr;
+ int phy_id = tp->mii_if.phy_id;
+ u16 reg[2][2] = {
+ { 0x808b, 0x0ce1 },
+ { 0x808f, 0x0c60 }
+ }, *p;
+
+ p = (tp->features & F_HAS_RGMII) ? reg[0] : reg[1];
+
+ mdio_write(ioaddr, phy_id, 0x1b, p[0]);
+ udelay(200);
+ mdio_write(ioaddr, phy_id, 0x14, p[1]);
+ udelay(200);
+ }
+}
+
+/**
+ * sis190_mii_probe - Probe MII PHY for sis190
+ * @dev: the net device to probe for
+ *
+ * Search for total of 32 possible mii phy addresses.
+ * Identify and set current phy if found one,
+ * return error if it failed to found.
+ */
+static int __devinit sis190_mii_probe(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ struct mii_if_info *mii_if = &tp->mii_if;
+ void __iomem *ioaddr = tp->mmio_addr;
+ int phy_id;
+ int rc = 0;
+
+ INIT_LIST_HEAD(&tp->first_phy);
+
+ for (phy_id = 0; phy_id < PHY_MAX_ADDR; phy_id++) {
+ struct sis190_phy *phy;
+ u16 status;
+
+ status = mdio_read_latched(ioaddr, phy_id, MII_BMSR);
+
+ // Try next mii if the current one is not accessible.
+ if (status == 0xffff || status == 0x0000)
+ continue;
+
+ phy = kmalloc(sizeof(*phy), GFP_KERNEL);
+ if (!phy) {
+ sis190_free_phy(&tp->first_phy);
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ sis190_init_phy(dev, tp, phy, phy_id, status);
+
+ list_add(&tp->first_phy, &phy->list);
+ }
+
+ if (list_empty(&tp->first_phy)) {
+ if (netif_msg_probe(tp))
+ pr_info("%s: No MII transceivers found!\n",
+ pci_name(tp->pci_dev));
+ rc = -EIO;
+ goto out;
+ }
+
+ /* Select default PHY for mac */
+ sis190_default_phy(dev);
+
+ sis190_mii_probe_88e1111_fixup(tp);
+
+ mii_if->dev = dev;
+ mii_if->mdio_read = __mdio_read;
+ mii_if->mdio_write = __mdio_write;
+ mii_if->phy_id_mask = PHY_ID_ANY;
+ mii_if->reg_num_mask = MII_REG_ANY;
+out:
+ return rc;
+}
+
+static void sis190_mii_remove(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ sis190_free_phy(&tp->first_phy);
+}
+
+static void sis190_release_board(struct pci_dev *pdev)
+{
+ struct net_device *dev = pci_get_drvdata(pdev);
+ struct sis190_private *tp = netdev_priv(dev);
+
+ iounmap(tp->mmio_addr);
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ free_netdev(dev);
+}
+
+static struct net_device * __devinit sis190_init_board(struct pci_dev *pdev)
+{
+ struct sis190_private *tp;
+ struct net_device *dev;
+ void __iomem *ioaddr;
+ int rc;
+
+ dev = alloc_etherdev(sizeof(*tp));
+ if (!dev) {
+ if (netif_msg_drv(&debug))
+ pr_err("unable to alloc new ethernet\n");
+ rc = -ENOMEM;
+ goto err_out_0;
+ }
+
+ SET_NETDEV_DEV(dev, &pdev->dev);
+
+ tp = netdev_priv(dev);
+ tp->dev = dev;
+ tp->msg_enable = netif_msg_init(debug.msg_enable, SIS190_MSG_DEFAULT);
+
+ rc = pci_enable_device(pdev);
+ if (rc < 0) {
+ if (netif_msg_probe(tp))
+ pr_err("%s: enable failure\n", pci_name(pdev));
+ goto err_free_dev_1;
+ }
+
+ rc = -ENODEV;
+
+ if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) {
+ if (netif_msg_probe(tp))
+ pr_err("%s: region #0 is no MMIO resource\n",
+ pci_name(pdev));
+ goto err_pci_disable_2;
+ }
+ if (pci_resource_len(pdev, 0) < SIS190_REGS_SIZE) {
+ if (netif_msg_probe(tp))
+ pr_err("%s: invalid PCI region size(s)\n",
+ pci_name(pdev));
+ goto err_pci_disable_2;
+ }
+
+ rc = pci_request_regions(pdev, DRV_NAME);
+ if (rc < 0) {
+ if (netif_msg_probe(tp))
+ pr_err("%s: could not request regions\n",
+ pci_name(pdev));
+ goto err_pci_disable_2;
+ }
+
+ rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
+ if (rc < 0) {
+ if (netif_msg_probe(tp))
+ pr_err("%s: DMA configuration failed\n",
+ pci_name(pdev));
+ goto err_free_res_3;
+ }
+
+ pci_set_master(pdev);
+
+ ioaddr = ioremap(pci_resource_start(pdev, 0), SIS190_REGS_SIZE);
+ if (!ioaddr) {
+ if (netif_msg_probe(tp))
+ pr_err("%s: cannot remap MMIO, aborting\n",
+ pci_name(pdev));
+ rc = -EIO;
+ goto err_free_res_3;
+ }
+
+ tp->pci_dev = pdev;
+ tp->mmio_addr = ioaddr;
+ tp->link_status = LNK_OFF;
+
+ sis190_irq_mask_and_ack(ioaddr);
+
+ sis190_soft_reset(ioaddr);
+out:
+ return dev;
+
+err_free_res_3:
+ pci_release_regions(pdev);
+err_pci_disable_2:
+ pci_disable_device(pdev);
+err_free_dev_1:
+ free_netdev(dev);
+err_out_0:
+ dev = ERR_PTR(rc);
+ goto out;
+}
+
+static void sis190_tx_timeout(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ void __iomem *ioaddr = tp->mmio_addr;
+ u8 tmp8;
+
+ /* Disable Tx, if not already */
+ tmp8 = SIS_R8(TxControl);
+ if (tmp8 & CmdTxEnb)
+ SIS_W8(TxControl, tmp8 & ~CmdTxEnb);
+
+ netif_info(tp, tx_err, dev, "Transmit timeout, status %08x %08x\n",
+ SIS_R32(TxControl), SIS_R32(TxSts));
+
+ /* Disable interrupts by clearing the interrupt mask. */
+ SIS_W32(IntrMask, 0x0000);
+
+ /* Stop a shared interrupt from scavenging while we are. */
+ spin_lock_irq(&tp->lock);
+ sis190_tx_clear(tp);
+ spin_unlock_irq(&tp->lock);
+
+ /* ...and finally, reset everything. */
+ sis190_hw_start(dev);
+
+ netif_wake_queue(dev);
+}
+
+static void sis190_set_rgmii(struct sis190_private *tp, u8 reg)
+{
+ tp->features |= (reg & 0x80) ? F_HAS_RGMII : 0;
+}
+
+static int __devinit sis190_get_mac_addr_from_eeprom(struct pci_dev *pdev,
+ struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ void __iomem *ioaddr = tp->mmio_addr;
+ u16 sig;
+ int i;
+
+ if (netif_msg_probe(tp))
+ pr_info("%s: Read MAC address from EEPROM\n", pci_name(pdev));
+
+ /* Check to see if there is a sane EEPROM */
+ sig = (u16) sis190_read_eeprom(ioaddr, EEPROMSignature);
+
+ if ((sig == 0xffff) || (sig == 0x0000)) {
+ if (netif_msg_probe(tp))
+ pr_info("%s: Error EEPROM read %x\n",
+ pci_name(pdev), sig);
+ return -EIO;
+ }
+
+ /* Get MAC address from EEPROM */
+ for (i = 0; i < MAC_ADDR_LEN / 2; i++) {
+ u16 w = sis190_read_eeprom(ioaddr, EEPROMMACAddr + i);
+
+ ((__le16 *)dev->dev_addr)[i] = cpu_to_le16(w);
+ }
+
+ sis190_set_rgmii(tp, sis190_read_eeprom(ioaddr, EEPROMInfo));
+
+ return 0;
+}
+
+/**
+ * sis190_get_mac_addr_from_apc - Get MAC address for SiS96x model
+ * @pdev: PCI device
+ * @dev: network device to get address for
+ *
+ * SiS96x model, use APC CMOS RAM to store MAC address.
+ * APC CMOS RAM is accessed through ISA bridge.
+ * MAC address is read into @net_dev->dev_addr.
+ */
+static int __devinit sis190_get_mac_addr_from_apc(struct pci_dev *pdev,
+ struct net_device *dev)
+{
+ static const u16 __devinitdata ids[] = { 0x0965, 0x0966, 0x0968 };
+ struct sis190_private *tp = netdev_priv(dev);
+ struct pci_dev *isa_bridge;
+ u8 reg, tmp8;
+ unsigned int i;
+
+ if (netif_msg_probe(tp))
+ pr_info("%s: Read MAC address from APC\n", pci_name(pdev));
+
+ for (i = 0; i < ARRAY_SIZE(ids); i++) {
+ isa_bridge = pci_get_device(PCI_VENDOR_ID_SI, ids[i], NULL);
+ if (isa_bridge)
+ break;
+ }
+
+ if (!isa_bridge) {
+ if (netif_msg_probe(tp))
+ pr_info("%s: Can not find ISA bridge\n",
+ pci_name(pdev));
+ return -EIO;
+ }
+
+ /* Enable port 78h & 79h to access APC Registers. */
+ pci_read_config_byte(isa_bridge, 0x48, &tmp8);
+ reg = (tmp8 & ~0x02);
+ pci_write_config_byte(isa_bridge, 0x48, reg);
+ udelay(50);
+ pci_read_config_byte(isa_bridge, 0x48, &reg);
+
+ for (i = 0; i < MAC_ADDR_LEN; i++) {
+ outb(0x9 + i, 0x78);
+ dev->dev_addr[i] = inb(0x79);
+ }
+
+ outb(0x12, 0x78);
+ reg = inb(0x79);
+
+ sis190_set_rgmii(tp, reg);
+
+ /* Restore the value to ISA Bridge */
+ pci_write_config_byte(isa_bridge, 0x48, tmp8);
+ pci_dev_put(isa_bridge);
+
+ return 0;
+}
+
+/**
+ * sis190_init_rxfilter - Initialize the Rx filter
+ * @dev: network device to initialize
+ *
+ * Set receive filter address to our MAC address
+ * and enable packet filtering.
+ */
+static inline void sis190_init_rxfilter(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ void __iomem *ioaddr = tp->mmio_addr;
+ u16 ctl;
+ int i;
+
+ ctl = SIS_R16(RxMacControl);
+ /*
+ * Disable packet filtering before setting filter.
+ * Note: SiS's driver writes 32 bits but RxMacControl is 16 bits
+ * only and followed by RxMacAddr (6 bytes). Strange. -- FR
+ */
+ SIS_W16(RxMacControl, ctl & ~0x0f00);
+
+ for (i = 0; i < MAC_ADDR_LEN; i++)
+ SIS_W8(RxMacAddr + i, dev->dev_addr[i]);
+
+ SIS_W16(RxMacControl, ctl);
+ SIS_PCI_COMMIT();
+}
+
+static int __devinit sis190_get_mac_addr(struct pci_dev *pdev,
+ struct net_device *dev)
+{
+ int rc;
+
+ rc = sis190_get_mac_addr_from_eeprom(pdev, dev);
+ if (rc < 0) {
+ u8 reg;
+
+ pci_read_config_byte(pdev, 0x73, &reg);
+
+ if (reg & 0x00000001)
+ rc = sis190_get_mac_addr_from_apc(pdev, dev);
+ }
+ return rc;
+}
+
+static void sis190_set_speed_auto(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ void __iomem *ioaddr = tp->mmio_addr;
+ int phy_id = tp->mii_if.phy_id;
+ int val;
+
+ netif_info(tp, link, dev, "Enabling Auto-negotiation\n");
+
+ val = mdio_read(ioaddr, phy_id, MII_ADVERTISE);
+
+ // Enable 10/100 Full/Half Mode, leave MII_ADVERTISE bit4:0
+ // unchanged.
+ mdio_write(ioaddr, phy_id, MII_ADVERTISE, (val & ADVERTISE_SLCT) |
+ ADVERTISE_100FULL | ADVERTISE_10FULL |
+ ADVERTISE_100HALF | ADVERTISE_10HALF);
+
+ // Enable 1000 Full Mode.
+ mdio_write(ioaddr, phy_id, MII_CTRL1000, ADVERTISE_1000FULL);
+
+ // Enable auto-negotiation and restart auto-negotiation.
+ mdio_write(ioaddr, phy_id, MII_BMCR,
+ BMCR_ANENABLE | BMCR_ANRESTART | BMCR_RESET);
+}
+
+static int sis190_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ return mii_ethtool_gset(&tp->mii_if, cmd);
+}
+
+static int sis190_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ return mii_ethtool_sset(&tp->mii_if, cmd);
+}
+
+static void sis190_get_drvinfo(struct net_device *dev,
+ struct ethtool_drvinfo *info)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ strcpy(info->driver, DRV_NAME);
+ strcpy(info->version, DRV_VERSION);
+ strcpy(info->bus_info, pci_name(tp->pci_dev));
+}
+
+static int sis190_get_regs_len(struct net_device *dev)
+{
+ return SIS190_REGS_SIZE;
+}
+
+static void sis190_get_regs(struct net_device *dev, struct ethtool_regs *regs,
+ void *p)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ unsigned long flags;
+
+ if (regs->len > SIS190_REGS_SIZE)
+ regs->len = SIS190_REGS_SIZE;
+
+ spin_lock_irqsave(&tp->lock, flags);
+ memcpy_fromio(p, tp->mmio_addr, regs->len);
+ spin_unlock_irqrestore(&tp->lock, flags);
+}
+
+static int sis190_nway_reset(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ return mii_nway_restart(&tp->mii_if);
+}
+
+static u32 sis190_get_msglevel(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ return tp->msg_enable;
+}
+
+static void sis190_set_msglevel(struct net_device *dev, u32 value)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ tp->msg_enable = value;
+}
+
+static const struct ethtool_ops sis190_ethtool_ops = {
+ .get_settings = sis190_get_settings,
+ .set_settings = sis190_set_settings,
+ .get_drvinfo = sis190_get_drvinfo,
+ .get_regs_len = sis190_get_regs_len,
+ .get_regs = sis190_get_regs,
+ .get_link = ethtool_op_get_link,
+ .get_msglevel = sis190_get_msglevel,
+ .set_msglevel = sis190_set_msglevel,
+ .nway_reset = sis190_nway_reset,
+};
+
+static int sis190_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ return !netif_running(dev) ? -EINVAL :
+ generic_mii_ioctl(&tp->mii_if, if_mii(ifr), cmd, NULL);
+}
+
+static int sis190_mac_addr(struct net_device *dev, void *p)
+{
+ int rc;
+
+ rc = eth_mac_addr(dev, p);
+ if (!rc)
+ sis190_init_rxfilter(dev);
+ return rc;
+}
+
+static const struct net_device_ops sis190_netdev_ops = {
+ .ndo_open = sis190_open,
+ .ndo_stop = sis190_close,
+ .ndo_do_ioctl = sis190_ioctl,
+ .ndo_start_xmit = sis190_start_xmit,
+ .ndo_tx_timeout = sis190_tx_timeout,
+ .ndo_set_multicast_list = sis190_set_rx_mode,
+ .ndo_change_mtu = eth_change_mtu,
+ .ndo_set_mac_address = sis190_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+ .ndo_poll_controller = sis190_netpoll,
+#endif
+};
+
+static int __devinit sis190_init_one(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ static int printed_version = 0;
+ struct sis190_private *tp;
+ struct net_device *dev;
+ void __iomem *ioaddr;
+ int rc;
+
+ if (!printed_version) {
+ if (netif_msg_drv(&debug))
+ pr_info(SIS190_DRIVER_NAME " loaded\n");
+ printed_version = 1;
+ }
+
+ dev = sis190_init_board(pdev);
+ if (IS_ERR(dev)) {
+ rc = PTR_ERR(dev);
+ goto out;
+ }
+
+ pci_set_drvdata(pdev, dev);
+
+ tp = netdev_priv(dev);
+ ioaddr = tp->mmio_addr;
+
+ rc = sis190_get_mac_addr(pdev, dev);
+ if (rc < 0)
+ goto err_release_board;
+
+ sis190_init_rxfilter(dev);
+
+ INIT_WORK(&tp->phy_task, sis190_phy_task);
+
+ dev->netdev_ops = &sis190_netdev_ops;
+
+ SET_ETHTOOL_OPS(dev, &sis190_ethtool_ops);
+ dev->irq = pdev->irq;
+ dev->base_addr = (unsigned long) 0xdead;
+ dev->watchdog_timeo = SIS190_TX_TIMEOUT;
+
+ spin_lock_init(&tp->lock);
+
+ rc = sis190_mii_probe(dev);
+ if (rc < 0)
+ goto err_release_board;
+
+ rc = register_netdev(dev);
+ if (rc < 0)
+ goto err_remove_mii;
+
+ if (netif_msg_probe(tp)) {
+ netdev_info(dev, "%s: %s at %p (IRQ: %d), %pM\n",
+ pci_name(pdev),
+ sis_chip_info[ent->driver_data].name,
+ ioaddr, dev->irq, dev->dev_addr);
+ netdev_info(dev, "%s mode.\n",
+ (tp->features & F_HAS_RGMII) ? "RGMII" : "GMII");
+ }
+
+ netif_carrier_off(dev);
+
+ sis190_set_speed_auto(dev);
+out:
+ return rc;
+
+err_remove_mii:
+ sis190_mii_remove(dev);
+err_release_board:
+ sis190_release_board(pdev);
+ goto out;
+}
+
+static void __devexit sis190_remove_one(struct pci_dev *pdev)
+{
+ struct net_device *dev = pci_get_drvdata(pdev);
+ struct sis190_private *tp = netdev_priv(dev);
+
+ sis190_mii_remove(dev);
+ cancel_work_sync(&tp->phy_task);
+ unregister_netdev(dev);
+ sis190_release_board(pdev);
+ pci_set_drvdata(pdev, NULL);
+}
+
+static struct pci_driver sis190_pci_driver = {
+ .name = DRV_NAME,
+ .id_table = sis190_pci_tbl,
+ .probe = sis190_init_one,
+ .remove = __devexit_p(sis190_remove_one),
+};
+
+static int __init sis190_init_module(void)
+{
+ return pci_register_driver(&sis190_pci_driver);
+}
+
+static void __exit sis190_cleanup_module(void)
+{
+ pci_unregister_driver(&sis190_pci_driver);
+}
+
+module_init(sis190_init_module);
+module_exit(sis190_cleanup_module);
diff --git a/drivers/net/ethernet/sis/sis900.c b/drivers/net/ethernet/sis/sis900.c
new file mode 100644
index 000000000000..658a1928fe79
--- /dev/null
+++ b/drivers/net/ethernet/sis/sis900.c
@@ -0,0 +1,2494 @@
+/* sis900.c: A SiS 900/7016 PCI Fast Ethernet driver for Linux.
+ Copyright 1999 Silicon Integrated System Corporation
+ Revision: 1.08.10 Apr. 2 2006
+
+ Modified from the driver which is originally written by Donald Becker.
+
+ This software may be used and distributed according to the terms
+ of the GNU General Public License (GPL), incorporated herein by reference.
+ Drivers based on this skeleton fall under the GPL and must retain
+ the authorship (implicit copyright) notice.
+
+ References:
+ SiS 7016 Fast Ethernet PCI Bus 10/100 Mbps LAN Controller with OnNow Support,
+ preliminary Rev. 1.0 Jan. 14, 1998
+ SiS 900 Fast Ethernet PCI Bus 10/100 Mbps LAN Single Chip with OnNow Support,
+ preliminary Rev. 1.0 Nov. 10, 1998
+ SiS 7014 Single Chip 100BASE-TX/10BASE-T Physical Layer Solution,
+ preliminary Rev. 1.0 Jan. 18, 1998
+
+ Rev 1.08.10 Apr. 2 2006 Daniele Venzano add vlan (jumbo packets) support
+ Rev 1.08.09 Sep. 19 2005 Daniele Venzano add Wake on LAN support
+ Rev 1.08.08 Jan. 22 2005 Daniele Venzano use netif_msg for debugging messages
+ Rev 1.08.07 Nov. 2 2003 Daniele Venzano <venza@brownhat.org> add suspend/resume support
+ Rev 1.08.06 Sep. 24 2002 Mufasa Yang bug fix for Tx timeout & add SiS963 support
+ Rev 1.08.05 Jun. 6 2002 Mufasa Yang bug fix for read_eeprom & Tx descriptor over-boundary
+ Rev 1.08.04 Apr. 25 2002 Mufasa Yang <mufasa@sis.com.tw> added SiS962 support
+ Rev 1.08.03 Feb. 1 2002 Matt Domsch <Matt_Domsch@dell.com> update to use library crc32 function
+ Rev 1.08.02 Nov. 30 2001 Hui-Fen Hsu workaround for EDB & bug fix for dhcp problem
+ Rev 1.08.01 Aug. 25 2001 Hui-Fen Hsu update for 630ET & workaround for ICS1893 PHY
+ Rev 1.08.00 Jun. 11 2001 Hui-Fen Hsu workaround for RTL8201 PHY and some bug fix
+ Rev 1.07.11 Apr. 2 2001 Hui-Fen Hsu updates PCI drivers to use the new pci_set_dma_mask for kernel 2.4.3
+ Rev 1.07.10 Mar. 1 2001 Hui-Fen Hsu <hfhsu@sis.com.tw> some bug fix & 635M/B support
+ Rev 1.07.09 Feb. 9 2001 Dave Jones <davej@suse.de> PCI enable cleanup
+ Rev 1.07.08 Jan. 8 2001 Lei-Chun Chang added RTL8201 PHY support
+ Rev 1.07.07 Nov. 29 2000 Lei-Chun Chang added kernel-doc extractable documentation and 630 workaround fix
+ Rev 1.07.06 Nov. 7 2000 Jeff Garzik <jgarzik@pobox.com> some bug fix and cleaning
+ Rev 1.07.05 Nov. 6 2000 metapirat<metapirat@gmx.de> contribute media type select by ifconfig
+ Rev 1.07.04 Sep. 6 2000 Lei-Chun Chang added ICS1893 PHY support
+ Rev 1.07.03 Aug. 24 2000 Lei-Chun Chang (lcchang@sis.com.tw) modified 630E equalizer workaround rule
+ Rev 1.07.01 Aug. 08 2000 Ollie Lho minor update for SiS 630E and SiS 630E A1
+ Rev 1.07 Mar. 07 2000 Ollie Lho bug fix in Rx buffer ring
+ Rev 1.06.04 Feb. 11 2000 Jeff Garzik <jgarzik@pobox.com> softnet and init for kernel 2.4
+ Rev 1.06.03 Dec. 23 1999 Ollie Lho Third release
+ Rev 1.06.02 Nov. 23 1999 Ollie Lho bug in mac probing fixed
+ Rev 1.06.01 Nov. 16 1999 Ollie Lho CRC calculation provide by Joseph Zbiciak (im14u2c@primenet.com)
+ Rev 1.06 Nov. 4 1999 Ollie Lho (ollie@sis.com.tw) Second release
+ Rev 1.05.05 Oct. 29 1999 Ollie Lho (ollie@sis.com.tw) Single buffer Tx/Rx
+ Chin-Shan Li (lcs@sis.com.tw) Added AMD Am79c901 HomePNA PHY support
+ Rev 1.05 Aug. 7 1999 Jim Huang (cmhuang@sis.com.tw) Initial release
+*/
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/netdevice.h>
+#include <linux/init.h>
+#include <linux/mii.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/delay.h>
+#include <linux/ethtool.h>
+#include <linux/crc32.h>
+#include <linux/bitops.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/processor.h> /* Processor type for cache alignment. */
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h> /* User space memory access functions */
+
+#include "sis900.h"
+
+#define SIS900_MODULE_NAME "sis900"
+#define SIS900_DRV_VERSION "v1.08.10 Apr. 2 2006"
+
+static const char version[] __devinitconst =
+ KERN_INFO "sis900.c: " SIS900_DRV_VERSION "\n";
+
+static int max_interrupt_work = 40;
+static int multicast_filter_limit = 128;
+
+static int sis900_debug = -1; /* Use SIS900_DEF_MSG as value */
+
+#define SIS900_DEF_MSG \
+ (NETIF_MSG_DRV | \
+ NETIF_MSG_LINK | \
+ NETIF_MSG_RX_ERR | \
+ NETIF_MSG_TX_ERR)
+
+/* Time in jiffies before concluding the transmitter is hung. */
+#define TX_TIMEOUT (4*HZ)
+
+enum {
+ SIS_900 = 0,
+ SIS_7016
+};
+static const char * card_names[] = {
+ "SiS 900 PCI Fast Ethernet",
+ "SiS 7016 PCI Fast Ethernet"
+};
+static DEFINE_PCI_DEVICE_TABLE(sis900_pci_tbl) = {
+ {PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_900,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, SIS_900},
+ {PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_7016,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, SIS_7016},
+ {0,}
+};
+MODULE_DEVICE_TABLE (pci, sis900_pci_tbl);
+
+static void sis900_read_mode(struct net_device *net_dev, int *speed, int *duplex);
+
+static const struct mii_chip_info {
+ const char * name;
+ u16 phy_id0;
+ u16 phy_id1;
+ u8 phy_types;
+#define HOME 0x0001
+#define LAN 0x0002
+#define MIX 0x0003
+#define UNKNOWN 0x0
+} mii_chip_table[] = {
+ { "SiS 900 Internal MII PHY", 0x001d, 0x8000, LAN },
+ { "SiS 7014 Physical Layer Solution", 0x0016, 0xf830, LAN },
+ { "SiS 900 on Foxconn 661 7MI", 0x0143, 0xBC70, LAN },
+ { "Altimata AC101LF PHY", 0x0022, 0x5520, LAN },
+ { "ADM 7001 LAN PHY", 0x002e, 0xcc60, LAN },
+ { "AMD 79C901 10BASE-T PHY", 0x0000, 0x6B70, LAN },
+ { "AMD 79C901 HomePNA PHY", 0x0000, 0x6B90, HOME},
+ { "ICS LAN PHY", 0x0015, 0xF440, LAN },
+ { "ICS LAN PHY", 0x0143, 0xBC70, LAN },
+ { "NS 83851 PHY", 0x2000, 0x5C20, MIX },
+ { "NS 83847 PHY", 0x2000, 0x5C30, MIX },
+ { "Realtek RTL8201 PHY", 0x0000, 0x8200, LAN },
+ { "VIA 6103 PHY", 0x0101, 0x8f20, LAN },
+ {NULL,},
+};
+
+struct mii_phy {
+ struct mii_phy * next;
+ int phy_addr;
+ u16 phy_id0;
+ u16 phy_id1;
+ u16 status;
+ u8 phy_types;
+};
+
+typedef struct _BufferDesc {
+ u32 link;
+ u32 cmdsts;
+ u32 bufptr;
+} BufferDesc;
+
+struct sis900_private {
+ struct pci_dev * pci_dev;
+
+ spinlock_t lock;
+
+ struct mii_phy * mii;
+ struct mii_phy * first_mii; /* record the first mii structure */
+ unsigned int cur_phy;
+ struct mii_if_info mii_info;
+
+ struct timer_list timer; /* Link status detection timer. */
+ u8 autong_complete; /* 1: auto-negotiate complete */
+
+ u32 msg_enable;
+
+ unsigned int cur_rx, dirty_rx; /* producer/comsumer pointers for Tx/Rx ring */
+ unsigned int cur_tx, dirty_tx;
+
+ /* The saved address of a sent/receive-in-place packet buffer */
+ struct sk_buff *tx_skbuff[NUM_TX_DESC];
+ struct sk_buff *rx_skbuff[NUM_RX_DESC];
+ BufferDesc *tx_ring;
+ BufferDesc *rx_ring;
+
+ dma_addr_t tx_ring_dma;
+ dma_addr_t rx_ring_dma;
+
+ unsigned int tx_full; /* The Tx queue is full. */
+ u8 host_bridge_rev;
+ u8 chipset_rev;
+};
+
+MODULE_AUTHOR("Jim Huang <cmhuang@sis.com.tw>, Ollie Lho <ollie@sis.com.tw>");
+MODULE_DESCRIPTION("SiS 900 PCI Fast Ethernet driver");
+MODULE_LICENSE("GPL");
+
+module_param(multicast_filter_limit, int, 0444);
+module_param(max_interrupt_work, int, 0444);
+module_param(sis900_debug, int, 0444);
+MODULE_PARM_DESC(multicast_filter_limit, "SiS 900/7016 maximum number of filtered multicast addresses");
+MODULE_PARM_DESC(max_interrupt_work, "SiS 900/7016 maximum events handled per interrupt");
+MODULE_PARM_DESC(sis900_debug, "SiS 900/7016 bitmapped debugging message level");
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+static void sis900_poll(struct net_device *dev);
+#endif
+static int sis900_open(struct net_device *net_dev);
+static int sis900_mii_probe (struct net_device * net_dev);
+static void sis900_init_rxfilter (struct net_device * net_dev);
+static u16 read_eeprom(long ioaddr, int location);
+static int mdio_read(struct net_device *net_dev, int phy_id, int location);
+static void mdio_write(struct net_device *net_dev, int phy_id, int location, int val);
+static void sis900_timer(unsigned long data);
+static void sis900_check_mode (struct net_device *net_dev, struct mii_phy *mii_phy);
+static void sis900_tx_timeout(struct net_device *net_dev);
+static void sis900_init_tx_ring(struct net_device *net_dev);
+static void sis900_init_rx_ring(struct net_device *net_dev);
+static netdev_tx_t sis900_start_xmit(struct sk_buff *skb,
+ struct net_device *net_dev);
+static int sis900_rx(struct net_device *net_dev);
+static void sis900_finish_xmit (struct net_device *net_dev);
+static irqreturn_t sis900_interrupt(int irq, void *dev_instance);
+static int sis900_close(struct net_device *net_dev);
+static int mii_ioctl(struct net_device *net_dev, struct ifreq *rq, int cmd);
+static u16 sis900_mcast_bitnr(u8 *addr, u8 revision);
+static void set_rx_mode(struct net_device *net_dev);
+static void sis900_reset(struct net_device *net_dev);
+static void sis630_set_eq(struct net_device *net_dev, u8 revision);
+static int sis900_set_config(struct net_device *dev, struct ifmap *map);
+static u16 sis900_default_phy(struct net_device * net_dev);
+static void sis900_set_capability( struct net_device *net_dev ,struct mii_phy *phy);
+static u16 sis900_reset_phy(struct net_device *net_dev, int phy_addr);
+static void sis900_auto_negotiate(struct net_device *net_dev, int phy_addr);
+static void sis900_set_mode (long ioaddr, int speed, int duplex);
+static const struct ethtool_ops sis900_ethtool_ops;
+
+/**
+ * sis900_get_mac_addr - Get MAC address for stand alone SiS900 model
+ * @pci_dev: the sis900 pci device
+ * @net_dev: the net device to get address for
+ *
+ * Older SiS900 and friends, use EEPROM to store MAC address.
+ * MAC address is read from read_eeprom() into @net_dev->dev_addr and
+ * @net_dev->perm_addr.
+ */
+
+static int __devinit sis900_get_mac_addr(struct pci_dev * pci_dev, struct net_device *net_dev)
+{
+ long ioaddr = pci_resource_start(pci_dev, 0);
+ u16 signature;
+ int i;
+
+ /* check to see if we have sane EEPROM */
+ signature = (u16) read_eeprom(ioaddr, EEPROMSignature);
+ if (signature == 0xffff || signature == 0x0000) {
+ printk (KERN_WARNING "%s: Error EERPOM read %x\n",
+ pci_name(pci_dev), signature);
+ return 0;
+ }
+
+ /* get MAC address from EEPROM */
+ for (i = 0; i < 3; i++)
+ ((u16 *)(net_dev->dev_addr))[i] = read_eeprom(ioaddr, i+EEPROMMACAddr);
+
+ /* Store MAC Address in perm_addr */
+ memcpy(net_dev->perm_addr, net_dev->dev_addr, ETH_ALEN);
+
+ return 1;
+}
+
+/**
+ * sis630e_get_mac_addr - Get MAC address for SiS630E model
+ * @pci_dev: the sis900 pci device
+ * @net_dev: the net device to get address for
+ *
+ * SiS630E model, use APC CMOS RAM to store MAC address.
+ * APC CMOS RAM is accessed through ISA bridge.
+ * MAC address is read into @net_dev->dev_addr and
+ * @net_dev->perm_addr.
+ */
+
+static int __devinit sis630e_get_mac_addr(struct pci_dev * pci_dev,
+ struct net_device *net_dev)
+{
+ struct pci_dev *isa_bridge = NULL;
+ u8 reg;
+ int i;
+
+ isa_bridge = pci_get_device(PCI_VENDOR_ID_SI, 0x0008, isa_bridge);
+ if (!isa_bridge)
+ isa_bridge = pci_get_device(PCI_VENDOR_ID_SI, 0x0018, isa_bridge);
+ if (!isa_bridge) {
+ printk(KERN_WARNING "%s: Can not find ISA bridge\n",
+ pci_name(pci_dev));
+ return 0;
+ }
+ pci_read_config_byte(isa_bridge, 0x48, &reg);
+ pci_write_config_byte(isa_bridge, 0x48, reg | 0x40);
+
+ for (i = 0; i < 6; i++) {
+ outb(0x09 + i, 0x70);
+ ((u8 *)(net_dev->dev_addr))[i] = inb(0x71);
+ }
+
+ /* Store MAC Address in perm_addr */
+ memcpy(net_dev->perm_addr, net_dev->dev_addr, ETH_ALEN);
+
+ pci_write_config_byte(isa_bridge, 0x48, reg & ~0x40);
+ pci_dev_put(isa_bridge);
+
+ return 1;
+}
+
+
+/**
+ * sis635_get_mac_addr - Get MAC address for SIS635 model
+ * @pci_dev: the sis900 pci device
+ * @net_dev: the net device to get address for
+ *
+ * SiS635 model, set MAC Reload Bit to load Mac address from APC
+ * to rfdr. rfdr is accessed through rfcr. MAC address is read into
+ * @net_dev->dev_addr and @net_dev->perm_addr.
+ */
+
+static int __devinit sis635_get_mac_addr(struct pci_dev * pci_dev,
+ struct net_device *net_dev)
+{
+ long ioaddr = net_dev->base_addr;
+ u32 rfcrSave;
+ u32 i;
+
+ rfcrSave = inl(rfcr + ioaddr);
+
+ outl(rfcrSave | RELOAD, ioaddr + cr);
+ outl(0, ioaddr + cr);
+
+ /* disable packet filtering before setting filter */
+ outl(rfcrSave & ~RFEN, rfcr + ioaddr);
+
+ /* load MAC addr to filter data register */
+ for (i = 0 ; i < 3 ; i++) {
+ outl((i << RFADDR_shift), ioaddr + rfcr);
+ *( ((u16 *)net_dev->dev_addr) + i) = inw(ioaddr + rfdr);
+ }
+
+ /* Store MAC Address in perm_addr */
+ memcpy(net_dev->perm_addr, net_dev->dev_addr, ETH_ALEN);
+
+ /* enable packet filtering */
+ outl(rfcrSave | RFEN, rfcr + ioaddr);
+
+ return 1;
+}
+
+/**
+ * sis96x_get_mac_addr - Get MAC address for SiS962 or SiS963 model
+ * @pci_dev: the sis900 pci device
+ * @net_dev: the net device to get address for
+ *
+ * SiS962 or SiS963 model, use EEPROM to store MAC address. And EEPROM
+ * is shared by
+ * LAN and 1394. When access EEPROM, send EEREQ signal to hardware first
+ * and wait for EEGNT. If EEGNT is ON, EEPROM is permitted to be access
+ * by LAN, otherwise is not. After MAC address is read from EEPROM, send
+ * EEDONE signal to refuse EEPROM access by LAN.
+ * The EEPROM map of SiS962 or SiS963 is different to SiS900.
+ * The signature field in SiS962 or SiS963 spec is meaningless.
+ * MAC address is read into @net_dev->dev_addr and @net_dev->perm_addr.
+ */
+
+static int __devinit sis96x_get_mac_addr(struct pci_dev * pci_dev,
+ struct net_device *net_dev)
+{
+ long ioaddr = net_dev->base_addr;
+ long ee_addr = ioaddr + mear;
+ u32 waittime = 0;
+ int i;
+
+ outl(EEREQ, ee_addr);
+ while(waittime < 2000) {
+ if(inl(ee_addr) & EEGNT) {
+
+ /* get MAC address from EEPROM */
+ for (i = 0; i < 3; i++)
+ ((u16 *)(net_dev->dev_addr))[i] = read_eeprom(ioaddr, i+EEPROMMACAddr);
+
+ /* Store MAC Address in perm_addr */
+ memcpy(net_dev->perm_addr, net_dev->dev_addr, ETH_ALEN);
+
+ outl(EEDONE, ee_addr);
+ return 1;
+ } else {
+ udelay(1);
+ waittime ++;
+ }
+ }
+ outl(EEDONE, ee_addr);
+ return 0;
+}
+
+static const struct net_device_ops sis900_netdev_ops = {
+ .ndo_open = sis900_open,
+ .ndo_stop = sis900_close,
+ .ndo_start_xmit = sis900_start_xmit,
+ .ndo_set_config = sis900_set_config,
+ .ndo_set_multicast_list = set_rx_mode,
+ .ndo_change_mtu = eth_change_mtu,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_do_ioctl = mii_ioctl,
+ .ndo_tx_timeout = sis900_tx_timeout,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+ .ndo_poll_controller = sis900_poll,
+#endif
+};
+
+/**
+ * sis900_probe - Probe for sis900 device
+ * @pci_dev: the sis900 pci device
+ * @pci_id: the pci device ID
+ *
+ * Check and probe sis900 net device for @pci_dev.
+ * Get mac address according to the chip revision,
+ * and assign SiS900-specific entries in the device structure.
+ * ie: sis900_open(), sis900_start_xmit(), sis900_close(), etc.
+ */
+
+static int __devinit sis900_probe(struct pci_dev *pci_dev,
+ const struct pci_device_id *pci_id)
+{
+ struct sis900_private *sis_priv;
+ struct net_device *net_dev;
+ struct pci_dev *dev;
+ dma_addr_t ring_dma;
+ void *ring_space;
+ long ioaddr;
+ int i, ret;
+ const char *card_name = card_names[pci_id->driver_data];
+ const char *dev_name = pci_name(pci_dev);
+
+/* when built into the kernel, we only print version if device is found */
+#ifndef MODULE
+ static int printed_version;
+ if (!printed_version++)
+ printk(version);
+#endif
+
+ /* setup various bits in PCI command register */
+ ret = pci_enable_device(pci_dev);
+ if(ret) return ret;
+
+ i = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32));
+ if(i){
+ printk(KERN_ERR "sis900.c: architecture does not support "
+ "32bit PCI busmaster DMA\n");
+ return i;
+ }
+
+ pci_set_master(pci_dev);
+
+ net_dev = alloc_etherdev(sizeof(struct sis900_private));
+ if (!net_dev)
+ return -ENOMEM;
+ SET_NETDEV_DEV(net_dev, &pci_dev->dev);
+
+ /* We do a request_region() to register /proc/ioports info. */
+ ioaddr = pci_resource_start(pci_dev, 0);
+ ret = pci_request_regions(pci_dev, "sis900");
+ if (ret)
+ goto err_out;
+
+ sis_priv = netdev_priv(net_dev);
+ net_dev->base_addr = ioaddr;
+ net_dev->irq = pci_dev->irq;
+ sis_priv->pci_dev = pci_dev;
+ spin_lock_init(&sis_priv->lock);
+
+ pci_set_drvdata(pci_dev, net_dev);
+
+ ring_space = pci_alloc_consistent(pci_dev, TX_TOTAL_SIZE, &ring_dma);
+ if (!ring_space) {
+ ret = -ENOMEM;
+ goto err_out_cleardev;
+ }
+ sis_priv->tx_ring = ring_space;
+ sis_priv->tx_ring_dma = ring_dma;
+
+ ring_space = pci_alloc_consistent(pci_dev, RX_TOTAL_SIZE, &ring_dma);
+ if (!ring_space) {
+ ret = -ENOMEM;
+ goto err_unmap_tx;
+ }
+ sis_priv->rx_ring = ring_space;
+ sis_priv->rx_ring_dma = ring_dma;
+
+ /* The SiS900-specific entries in the device structure. */
+ net_dev->netdev_ops = &sis900_netdev_ops;
+ net_dev->watchdog_timeo = TX_TIMEOUT;
+ net_dev->ethtool_ops = &sis900_ethtool_ops;
+
+ if (sis900_debug > 0)
+ sis_priv->msg_enable = sis900_debug;
+ else
+ sis_priv->msg_enable = SIS900_DEF_MSG;
+
+ sis_priv->mii_info.dev = net_dev;
+ sis_priv->mii_info.mdio_read = mdio_read;
+ sis_priv->mii_info.mdio_write = mdio_write;
+ sis_priv->mii_info.phy_id_mask = 0x1f;
+ sis_priv->mii_info.reg_num_mask = 0x1f;
+
+ /* Get Mac address according to the chip revision */
+ sis_priv->chipset_rev = pci_dev->revision;
+ if(netif_msg_probe(sis_priv))
+ printk(KERN_DEBUG "%s: detected revision %2.2x, "
+ "trying to get MAC address...\n",
+ dev_name, sis_priv->chipset_rev);
+
+ ret = 0;
+ if (sis_priv->chipset_rev == SIS630E_900_REV)
+ ret = sis630e_get_mac_addr(pci_dev, net_dev);
+ else if ((sis_priv->chipset_rev > 0x81) && (sis_priv->chipset_rev <= 0x90) )
+ ret = sis635_get_mac_addr(pci_dev, net_dev);
+ else if (sis_priv->chipset_rev == SIS96x_900_REV)
+ ret = sis96x_get_mac_addr(pci_dev, net_dev);
+ else
+ ret = sis900_get_mac_addr(pci_dev, net_dev);
+
+ if (!ret || !is_valid_ether_addr(net_dev->dev_addr)) {
+ random_ether_addr(net_dev->dev_addr);
+ printk(KERN_WARNING "%s: Unreadable or invalid MAC address,"
+ "using random generated one\n", dev_name);
+ }
+
+ /* 630ET : set the mii access mode as software-mode */
+ if (sis_priv->chipset_rev == SIS630ET_900_REV)
+ outl(ACCESSMODE | inl(ioaddr + cr), ioaddr + cr);
+
+ /* probe for mii transceiver */
+ if (sis900_mii_probe(net_dev) == 0) {
+ printk(KERN_WARNING "%s: Error probing MII device.\n",
+ dev_name);
+ ret = -ENODEV;
+ goto err_unmap_rx;
+ }
+
+ /* save our host bridge revision */
+ dev = pci_get_device(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_630, NULL);
+ if (dev) {
+ sis_priv->host_bridge_rev = dev->revision;
+ pci_dev_put(dev);
+ }
+
+ ret = register_netdev(net_dev);
+ if (ret)
+ goto err_unmap_rx;
+
+ /* print some information about our NIC */
+ printk(KERN_INFO "%s: %s at %#lx, IRQ %d, %pM\n",
+ net_dev->name, card_name, ioaddr, net_dev->irq,
+ net_dev->dev_addr);
+
+ /* Detect Wake on Lan support */
+ ret = (inl(net_dev->base_addr + CFGPMC) & PMESP) >> 27;
+ if (netif_msg_probe(sis_priv) && (ret & PME_D3C) == 0)
+ printk(KERN_INFO "%s: Wake on LAN only available from suspend to RAM.", net_dev->name);
+
+ return 0;
+
+ err_unmap_rx:
+ pci_free_consistent(pci_dev, RX_TOTAL_SIZE, sis_priv->rx_ring,
+ sis_priv->rx_ring_dma);
+ err_unmap_tx:
+ pci_free_consistent(pci_dev, TX_TOTAL_SIZE, sis_priv->tx_ring,
+ sis_priv->tx_ring_dma);
+ err_out_cleardev:
+ pci_set_drvdata(pci_dev, NULL);
+ pci_release_regions(pci_dev);
+ err_out:
+ free_netdev(net_dev);
+ return ret;
+}
+
+/**
+ * sis900_mii_probe - Probe MII PHY for sis900
+ * @net_dev: the net device to probe for
+ *
+ * Search for total of 32 possible mii phy addresses.
+ * Identify and set current phy if found one,
+ * return error if it failed to found.
+ */
+
+static int __devinit sis900_mii_probe(struct net_device * net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ const char *dev_name = pci_name(sis_priv->pci_dev);
+ u16 poll_bit = MII_STAT_LINK, status = 0;
+ unsigned long timeout = jiffies + 5 * HZ;
+ int phy_addr;
+
+ sis_priv->mii = NULL;
+
+ /* search for total of 32 possible mii phy addresses */
+ for (phy_addr = 0; phy_addr < 32; phy_addr++) {
+ struct mii_phy * mii_phy = NULL;
+ u16 mii_status;
+ int i;
+
+ mii_phy = NULL;
+ for(i = 0; i < 2; i++)
+ mii_status = mdio_read(net_dev, phy_addr, MII_STATUS);
+
+ if (mii_status == 0xffff || mii_status == 0x0000) {
+ if (netif_msg_probe(sis_priv))
+ printk(KERN_DEBUG "%s: MII at address %d"
+ " not accessible\n",
+ dev_name, phy_addr);
+ continue;
+ }
+
+ if ((mii_phy = kmalloc(sizeof(struct mii_phy), GFP_KERNEL)) == NULL) {
+ printk(KERN_WARNING "Cannot allocate mem for struct mii_phy\n");
+ mii_phy = sis_priv->first_mii;
+ while (mii_phy) {
+ struct mii_phy *phy;
+ phy = mii_phy;
+ mii_phy = mii_phy->next;
+ kfree(phy);
+ }
+ return 0;
+ }
+
+ mii_phy->phy_id0 = mdio_read(net_dev, phy_addr, MII_PHY_ID0);
+ mii_phy->phy_id1 = mdio_read(net_dev, phy_addr, MII_PHY_ID1);
+ mii_phy->phy_addr = phy_addr;
+ mii_phy->status = mii_status;
+ mii_phy->next = sis_priv->mii;
+ sis_priv->mii = mii_phy;
+ sis_priv->first_mii = mii_phy;
+
+ for (i = 0; mii_chip_table[i].phy_id1; i++)
+ if ((mii_phy->phy_id0 == mii_chip_table[i].phy_id0 ) &&
+ ((mii_phy->phy_id1 & 0xFFF0) == mii_chip_table[i].phy_id1)){
+ mii_phy->phy_types = mii_chip_table[i].phy_types;
+ if (mii_chip_table[i].phy_types == MIX)
+ mii_phy->phy_types =
+ (mii_status & (MII_STAT_CAN_TX_FDX | MII_STAT_CAN_TX)) ? LAN : HOME;
+ printk(KERN_INFO "%s: %s transceiver found "
+ "at address %d.\n",
+ dev_name,
+ mii_chip_table[i].name,
+ phy_addr);
+ break;
+ }
+
+ if( !mii_chip_table[i].phy_id1 ) {
+ printk(KERN_INFO "%s: Unknown PHY transceiver found at address %d.\n",
+ dev_name, phy_addr);
+ mii_phy->phy_types = UNKNOWN;
+ }
+ }
+
+ if (sis_priv->mii == NULL) {
+ printk(KERN_INFO "%s: No MII transceivers found!\n", dev_name);
+ return 0;
+ }
+
+ /* select default PHY for mac */
+ sis_priv->mii = NULL;
+ sis900_default_phy( net_dev );
+
+ /* Reset phy if default phy is internal sis900 */
+ if ((sis_priv->mii->phy_id0 == 0x001D) &&
+ ((sis_priv->mii->phy_id1&0xFFF0) == 0x8000))
+ status = sis900_reset_phy(net_dev, sis_priv->cur_phy);
+
+ /* workaround for ICS1893 PHY */
+ if ((sis_priv->mii->phy_id0 == 0x0015) &&
+ ((sis_priv->mii->phy_id1&0xFFF0) == 0xF440))
+ mdio_write(net_dev, sis_priv->cur_phy, 0x0018, 0xD200);
+
+ if(status & MII_STAT_LINK){
+ while (poll_bit) {
+ yield();
+
+ poll_bit ^= (mdio_read(net_dev, sis_priv->cur_phy, MII_STATUS) & poll_bit);
+ if (time_after_eq(jiffies, timeout)) {
+ printk(KERN_WARNING "%s: reset phy and link down now\n",
+ dev_name);
+ return -ETIME;
+ }
+ }
+ }
+
+ if (sis_priv->chipset_rev == SIS630E_900_REV) {
+ /* SiS 630E has some bugs on default value of PHY registers */
+ mdio_write(net_dev, sis_priv->cur_phy, MII_ANADV, 0x05e1);
+ mdio_write(net_dev, sis_priv->cur_phy, MII_CONFIG1, 0x22);
+ mdio_write(net_dev, sis_priv->cur_phy, MII_CONFIG2, 0xff00);
+ mdio_write(net_dev, sis_priv->cur_phy, MII_MASK, 0xffc0);
+ //mdio_write(net_dev, sis_priv->cur_phy, MII_CONTROL, 0x1000);
+ }
+
+ if (sis_priv->mii->status & MII_STAT_LINK)
+ netif_carrier_on(net_dev);
+ else
+ netif_carrier_off(net_dev);
+
+ return 1;
+}
+
+/**
+ * sis900_default_phy - Select default PHY for sis900 mac.
+ * @net_dev: the net device to probe for
+ *
+ * Select first detected PHY with link as default.
+ * If no one is link on, select PHY whose types is HOME as default.
+ * If HOME doesn't exist, select LAN.
+ */
+
+static u16 sis900_default_phy(struct net_device * net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ struct mii_phy *phy = NULL, *phy_home = NULL,
+ *default_phy = NULL, *phy_lan = NULL;
+ u16 status;
+
+ for (phy=sis_priv->first_mii; phy; phy=phy->next) {
+ status = mdio_read(net_dev, phy->phy_addr, MII_STATUS);
+ status = mdio_read(net_dev, phy->phy_addr, MII_STATUS);
+
+ /* Link ON & Not select default PHY & not ghost PHY */
+ if ((status & MII_STAT_LINK) && !default_phy &&
+ (phy->phy_types != UNKNOWN))
+ default_phy = phy;
+ else {
+ status = mdio_read(net_dev, phy->phy_addr, MII_CONTROL);
+ mdio_write(net_dev, phy->phy_addr, MII_CONTROL,
+ status | MII_CNTL_AUTO | MII_CNTL_ISOLATE);
+ if (phy->phy_types == HOME)
+ phy_home = phy;
+ else if(phy->phy_types == LAN)
+ phy_lan = phy;
+ }
+ }
+
+ if (!default_phy && phy_home)
+ default_phy = phy_home;
+ else if (!default_phy && phy_lan)
+ default_phy = phy_lan;
+ else if (!default_phy)
+ default_phy = sis_priv->first_mii;
+
+ if (sis_priv->mii != default_phy) {
+ sis_priv->mii = default_phy;
+ sis_priv->cur_phy = default_phy->phy_addr;
+ printk(KERN_INFO "%s: Using transceiver found at address %d as default\n",
+ pci_name(sis_priv->pci_dev), sis_priv->cur_phy);
+ }
+
+ sis_priv->mii_info.phy_id = sis_priv->cur_phy;
+
+ status = mdio_read(net_dev, sis_priv->cur_phy, MII_CONTROL);
+ status &= (~MII_CNTL_ISOLATE);
+
+ mdio_write(net_dev, sis_priv->cur_phy, MII_CONTROL, status);
+ status = mdio_read(net_dev, sis_priv->cur_phy, MII_STATUS);
+ status = mdio_read(net_dev, sis_priv->cur_phy, MII_STATUS);
+
+ return status;
+}
+
+
+/**
+ * sis900_set_capability - set the media capability of network adapter.
+ * @net_dev : the net device to probe for
+ * @phy : default PHY
+ *
+ * Set the media capability of network adapter according to
+ * mii status register. It's necessary before auto-negotiate.
+ */
+
+static void sis900_set_capability(struct net_device *net_dev, struct mii_phy *phy)
+{
+ u16 cap;
+ u16 status;
+
+ status = mdio_read(net_dev, phy->phy_addr, MII_STATUS);
+ status = mdio_read(net_dev, phy->phy_addr, MII_STATUS);
+
+ cap = MII_NWAY_CSMA_CD |
+ ((phy->status & MII_STAT_CAN_TX_FDX)? MII_NWAY_TX_FDX:0) |
+ ((phy->status & MII_STAT_CAN_TX) ? MII_NWAY_TX:0) |
+ ((phy->status & MII_STAT_CAN_T_FDX) ? MII_NWAY_T_FDX:0)|
+ ((phy->status & MII_STAT_CAN_T) ? MII_NWAY_T:0);
+
+ mdio_write(net_dev, phy->phy_addr, MII_ANADV, cap);
+}
+
+
+/* Delay between EEPROM clock transitions. */
+#define eeprom_delay() inl(ee_addr)
+
+/**
+ * read_eeprom - Read Serial EEPROM
+ * @ioaddr: base i/o address
+ * @location: the EEPROM location to read
+ *
+ * Read Serial EEPROM through EEPROM Access Register.
+ * Note that location is in word (16 bits) unit
+ */
+
+static u16 __devinit read_eeprom(long ioaddr, int location)
+{
+ int i;
+ u16 retval = 0;
+ long ee_addr = ioaddr + mear;
+ u32 read_cmd = location | EEread;
+
+ outl(0, ee_addr);
+ eeprom_delay();
+ outl(EECS, ee_addr);
+ eeprom_delay();
+
+ /* Shift the read command (9) bits out. */
+ for (i = 8; i >= 0; i--) {
+ u32 dataval = (read_cmd & (1 << i)) ? EEDI | EECS : EECS;
+ outl(dataval, ee_addr);
+ eeprom_delay();
+ outl(dataval | EECLK, ee_addr);
+ eeprom_delay();
+ }
+ outl(EECS, ee_addr);
+ eeprom_delay();
+
+ /* read the 16-bits data in */
+ for (i = 16; i > 0; i--) {
+ outl(EECS, ee_addr);
+ eeprom_delay();
+ outl(EECS | EECLK, ee_addr);
+ eeprom_delay();
+ retval = (retval << 1) | ((inl(ee_addr) & EEDO) ? 1 : 0);
+ eeprom_delay();
+ }
+
+ /* Terminate the EEPROM access. */
+ outl(0, ee_addr);
+ eeprom_delay();
+
+ return retval;
+}
+
+/* Read and write the MII management registers using software-generated
+ serial MDIO protocol. Note that the command bits and data bits are
+ send out separately */
+#define mdio_delay() inl(mdio_addr)
+
+static void mdio_idle(long mdio_addr)
+{
+ outl(MDIO | MDDIR, mdio_addr);
+ mdio_delay();
+ outl(MDIO | MDDIR | MDC, mdio_addr);
+}
+
+/* Syncronize the MII management interface by shifting 32 one bits out. */
+static void mdio_reset(long mdio_addr)
+{
+ int i;
+
+ for (i = 31; i >= 0; i--) {
+ outl(MDDIR | MDIO, mdio_addr);
+ mdio_delay();
+ outl(MDDIR | MDIO | MDC, mdio_addr);
+ mdio_delay();
+ }
+}
+
+/**
+ * mdio_read - read MII PHY register
+ * @net_dev: the net device to read
+ * @phy_id: the phy address to read
+ * @location: the phy regiester id to read
+ *
+ * Read MII registers through MDIO and MDC
+ * using MDIO management frame structure and protocol(defined by ISO/IEC).
+ * Please see SiS7014 or ICS spec
+ */
+
+static int mdio_read(struct net_device *net_dev, int phy_id, int location)
+{
+ long mdio_addr = net_dev->base_addr + mear;
+ int mii_cmd = MIIread|(phy_id<<MIIpmdShift)|(location<<MIIregShift);
+ u16 retval = 0;
+ int i;
+
+ mdio_reset(mdio_addr);
+ mdio_idle(mdio_addr);
+
+ for (i = 15; i >= 0; i--) {
+ int dataval = (mii_cmd & (1 << i)) ? MDDIR | MDIO : MDDIR;
+ outl(dataval, mdio_addr);
+ mdio_delay();
+ outl(dataval | MDC, mdio_addr);
+ mdio_delay();
+ }
+
+ /* Read the 16 data bits. */
+ for (i = 16; i > 0; i--) {
+ outl(0, mdio_addr);
+ mdio_delay();
+ retval = (retval << 1) | ((inl(mdio_addr) & MDIO) ? 1 : 0);
+ outl(MDC, mdio_addr);
+ mdio_delay();
+ }
+ outl(0x00, mdio_addr);
+
+ return retval;
+}
+
+/**
+ * mdio_write - write MII PHY register
+ * @net_dev: the net device to write
+ * @phy_id: the phy address to write
+ * @location: the phy regiester id to write
+ * @value: the register value to write with
+ *
+ * Write MII registers with @value through MDIO and MDC
+ * using MDIO management frame structure and protocol(defined by ISO/IEC)
+ * please see SiS7014 or ICS spec
+ */
+
+static void mdio_write(struct net_device *net_dev, int phy_id, int location,
+ int value)
+{
+ long mdio_addr = net_dev->base_addr + mear;
+ int mii_cmd = MIIwrite|(phy_id<<MIIpmdShift)|(location<<MIIregShift);
+ int i;
+
+ mdio_reset(mdio_addr);
+ mdio_idle(mdio_addr);
+
+ /* Shift the command bits out. */
+ for (i = 15; i >= 0; i--) {
+ int dataval = (mii_cmd & (1 << i)) ? MDDIR | MDIO : MDDIR;
+ outb(dataval, mdio_addr);
+ mdio_delay();
+ outb(dataval | MDC, mdio_addr);
+ mdio_delay();
+ }
+ mdio_delay();
+
+ /* Shift the value bits out. */
+ for (i = 15; i >= 0; i--) {
+ int dataval = (value & (1 << i)) ? MDDIR | MDIO : MDDIR;
+ outl(dataval, mdio_addr);
+ mdio_delay();
+ outl(dataval | MDC, mdio_addr);
+ mdio_delay();
+ }
+ mdio_delay();
+
+ /* Clear out extra bits. */
+ for (i = 2; i > 0; i--) {
+ outb(0, mdio_addr);
+ mdio_delay();
+ outb(MDC, mdio_addr);
+ mdio_delay();
+ }
+ outl(0x00, mdio_addr);
+}
+
+
+/**
+ * sis900_reset_phy - reset sis900 mii phy.
+ * @net_dev: the net device to write
+ * @phy_addr: default phy address
+ *
+ * Some specific phy can't work properly without reset.
+ * This function will be called during initialization and
+ * link status change from ON to DOWN.
+ */
+
+static u16 sis900_reset_phy(struct net_device *net_dev, int phy_addr)
+{
+ int i;
+ u16 status;
+
+ for (i = 0; i < 2; i++)
+ status = mdio_read(net_dev, phy_addr, MII_STATUS);
+
+ mdio_write( net_dev, phy_addr, MII_CONTROL, MII_CNTL_RESET );
+
+ return status;
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+/*
+ * Polling 'interrupt' - used by things like netconsole to send skbs
+ * without having to re-enable interrupts. It's not called while
+ * the interrupt routine is executing.
+*/
+static void sis900_poll(struct net_device *dev)
+{
+ disable_irq(dev->irq);
+ sis900_interrupt(dev->irq, dev);
+ enable_irq(dev->irq);
+}
+#endif
+
+/**
+ * sis900_open - open sis900 device
+ * @net_dev: the net device to open
+ *
+ * Do some initialization and start net interface.
+ * enable interrupts and set sis900 timer.
+ */
+
+static int
+sis900_open(struct net_device *net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ long ioaddr = net_dev->base_addr;
+ int ret;
+
+ /* Soft reset the chip. */
+ sis900_reset(net_dev);
+
+ /* Equalizer workaround Rule */
+ sis630_set_eq(net_dev, sis_priv->chipset_rev);
+
+ ret = request_irq(net_dev->irq, sis900_interrupt, IRQF_SHARED,
+ net_dev->name, net_dev);
+ if (ret)
+ return ret;
+
+ sis900_init_rxfilter(net_dev);
+
+ sis900_init_tx_ring(net_dev);
+ sis900_init_rx_ring(net_dev);
+
+ set_rx_mode(net_dev);
+
+ netif_start_queue(net_dev);
+
+ /* Workaround for EDB */
+ sis900_set_mode(ioaddr, HW_SPEED_10_MBPS, FDX_CAPABLE_HALF_SELECTED);
+
+ /* Enable all known interrupts by setting the interrupt mask. */
+ outl((RxSOVR|RxORN|RxERR|RxOK|TxURN|TxERR|TxIDLE), ioaddr + imr);
+ outl(RxENA | inl(ioaddr + cr), ioaddr + cr);
+ outl(IE, ioaddr + ier);
+
+ sis900_check_mode(net_dev, sis_priv->mii);
+
+ /* Set the timer to switch to check for link beat and perhaps switch
+ to an alternate media type. */
+ init_timer(&sis_priv->timer);
+ sis_priv->timer.expires = jiffies + HZ;
+ sis_priv->timer.data = (unsigned long)net_dev;
+ sis_priv->timer.function = sis900_timer;
+ add_timer(&sis_priv->timer);
+
+ return 0;
+}
+
+/**
+ * sis900_init_rxfilter - Initialize the Rx filter
+ * @net_dev: the net device to initialize for
+ *
+ * Set receive filter address to our MAC address
+ * and enable packet filtering.
+ */
+
+static void
+sis900_init_rxfilter (struct net_device * net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ long ioaddr = net_dev->base_addr;
+ u32 rfcrSave;
+ u32 i;
+
+ rfcrSave = inl(rfcr + ioaddr);
+
+ /* disable packet filtering before setting filter */
+ outl(rfcrSave & ~RFEN, rfcr + ioaddr);
+
+ /* load MAC addr to filter data register */
+ for (i = 0 ; i < 3 ; i++) {
+ u32 w;
+
+ w = (u32) *((u16 *)(net_dev->dev_addr)+i);
+ outl((i << RFADDR_shift), ioaddr + rfcr);
+ outl(w, ioaddr + rfdr);
+
+ if (netif_msg_hw(sis_priv)) {
+ printk(KERN_DEBUG "%s: Receive Filter Addrss[%d]=%x\n",
+ net_dev->name, i, inl(ioaddr + rfdr));
+ }
+ }
+
+ /* enable packet filtering */
+ outl(rfcrSave | RFEN, rfcr + ioaddr);
+}
+
+/**
+ * sis900_init_tx_ring - Initialize the Tx descriptor ring
+ * @net_dev: the net device to initialize for
+ *
+ * Initialize the Tx descriptor ring,
+ */
+
+static void
+sis900_init_tx_ring(struct net_device *net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ long ioaddr = net_dev->base_addr;
+ int i;
+
+ sis_priv->tx_full = 0;
+ sis_priv->dirty_tx = sis_priv->cur_tx = 0;
+
+ for (i = 0; i < NUM_TX_DESC; i++) {
+ sis_priv->tx_skbuff[i] = NULL;
+
+ sis_priv->tx_ring[i].link = sis_priv->tx_ring_dma +
+ ((i+1)%NUM_TX_DESC)*sizeof(BufferDesc);
+ sis_priv->tx_ring[i].cmdsts = 0;
+ sis_priv->tx_ring[i].bufptr = 0;
+ }
+
+ /* load Transmit Descriptor Register */
+ outl(sis_priv->tx_ring_dma, ioaddr + txdp);
+ if (netif_msg_hw(sis_priv))
+ printk(KERN_DEBUG "%s: TX descriptor register loaded with: %8.8x\n",
+ net_dev->name, inl(ioaddr + txdp));
+}
+
+/**
+ * sis900_init_rx_ring - Initialize the Rx descriptor ring
+ * @net_dev: the net device to initialize for
+ *
+ * Initialize the Rx descriptor ring,
+ * and pre-allocate recevie buffers (socket buffer)
+ */
+
+static void
+sis900_init_rx_ring(struct net_device *net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ long ioaddr = net_dev->base_addr;
+ int i;
+
+ sis_priv->cur_rx = 0;
+ sis_priv->dirty_rx = 0;
+
+ /* init RX descriptor */
+ for (i = 0; i < NUM_RX_DESC; i++) {
+ sis_priv->rx_skbuff[i] = NULL;
+
+ sis_priv->rx_ring[i].link = sis_priv->rx_ring_dma +
+ ((i+1)%NUM_RX_DESC)*sizeof(BufferDesc);
+ sis_priv->rx_ring[i].cmdsts = 0;
+ sis_priv->rx_ring[i].bufptr = 0;
+ }
+
+ /* allocate sock buffers */
+ for (i = 0; i < NUM_RX_DESC; i++) {
+ struct sk_buff *skb;
+
+ if ((skb = dev_alloc_skb(RX_BUF_SIZE)) == NULL) {
+ /* not enough memory for skbuff, this makes a "hole"
+ on the buffer ring, it is not clear how the
+ hardware will react to this kind of degenerated
+ buffer */
+ break;
+ }
+ sis_priv->rx_skbuff[i] = skb;
+ sis_priv->rx_ring[i].cmdsts = RX_BUF_SIZE;
+ sis_priv->rx_ring[i].bufptr = pci_map_single(sis_priv->pci_dev,
+ skb->data, RX_BUF_SIZE, PCI_DMA_FROMDEVICE);
+ }
+ sis_priv->dirty_rx = (unsigned int) (i - NUM_RX_DESC);
+
+ /* load Receive Descriptor Register */
+ outl(sis_priv->rx_ring_dma, ioaddr + rxdp);
+ if (netif_msg_hw(sis_priv))
+ printk(KERN_DEBUG "%s: RX descriptor register loaded with: %8.8x\n",
+ net_dev->name, inl(ioaddr + rxdp));
+}
+
+/**
+ * sis630_set_eq - set phy equalizer value for 630 LAN
+ * @net_dev: the net device to set equalizer value
+ * @revision: 630 LAN revision number
+ *
+ * 630E equalizer workaround rule(Cyrus Huang 08/15)
+ * PHY register 14h(Test)
+ * Bit 14: 0 -- Automatically detect (default)
+ * 1 -- Manually set Equalizer filter
+ * Bit 13: 0 -- (Default)
+ * 1 -- Speed up convergence of equalizer setting
+ * Bit 9 : 0 -- (Default)
+ * 1 -- Disable Baseline Wander
+ * Bit 3~7 -- Equalizer filter setting
+ * Link ON: Set Bit 9, 13 to 1, Bit 14 to 0
+ * Then calculate equalizer value
+ * Then set equalizer value, and set Bit 14 to 1, Bit 9 to 0
+ * Link Off:Set Bit 13 to 1, Bit 14 to 0
+ * Calculate Equalizer value:
+ * When Link is ON and Bit 14 is 0, SIS900PHY will auto-detect proper equalizer value.
+ * When the equalizer is stable, this value is not a fixed value. It will be within
+ * a small range(eg. 7~9). Then we get a minimum and a maximum value(eg. min=7, max=9)
+ * 0 <= max <= 4 --> set equalizer to max
+ * 5 <= max <= 14 --> set equalizer to max+1 or set equalizer to max+2 if max == min
+ * max >= 15 --> set equalizer to max+5 or set equalizer to max+6 if max == min
+ */
+
+static void sis630_set_eq(struct net_device *net_dev, u8 revision)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ u16 reg14h, eq_value=0, max_value=0, min_value=0;
+ int i, maxcount=10;
+
+ if ( !(revision == SIS630E_900_REV || revision == SIS630EA1_900_REV ||
+ revision == SIS630A_900_REV || revision == SIS630ET_900_REV) )
+ return;
+
+ if (netif_carrier_ok(net_dev)) {
+ reg14h = mdio_read(net_dev, sis_priv->cur_phy, MII_RESV);
+ mdio_write(net_dev, sis_priv->cur_phy, MII_RESV,
+ (0x2200 | reg14h) & 0xBFFF);
+ for (i=0; i < maxcount; i++) {
+ eq_value = (0x00F8 & mdio_read(net_dev,
+ sis_priv->cur_phy, MII_RESV)) >> 3;
+ if (i == 0)
+ max_value=min_value=eq_value;
+ max_value = (eq_value > max_value) ?
+ eq_value : max_value;
+ min_value = (eq_value < min_value) ?
+ eq_value : min_value;
+ }
+ /* 630E rule to determine the equalizer value */
+ if (revision == SIS630E_900_REV || revision == SIS630EA1_900_REV ||
+ revision == SIS630ET_900_REV) {
+ if (max_value < 5)
+ eq_value = max_value;
+ else if (max_value >= 5 && max_value < 15)
+ eq_value = (max_value == min_value) ?
+ max_value+2 : max_value+1;
+ else if (max_value >= 15)
+ eq_value=(max_value == min_value) ?
+ max_value+6 : max_value+5;
+ }
+ /* 630B0&B1 rule to determine the equalizer value */
+ if (revision == SIS630A_900_REV &&
+ (sis_priv->host_bridge_rev == SIS630B0 ||
+ sis_priv->host_bridge_rev == SIS630B1)) {
+ if (max_value == 0)
+ eq_value = 3;
+ else
+ eq_value = (max_value + min_value + 1)/2;
+ }
+ /* write equalizer value and setting */
+ reg14h = mdio_read(net_dev, sis_priv->cur_phy, MII_RESV);
+ reg14h = (reg14h & 0xFF07) | ((eq_value << 3) & 0x00F8);
+ reg14h = (reg14h | 0x6000) & 0xFDFF;
+ mdio_write(net_dev, sis_priv->cur_phy, MII_RESV, reg14h);
+ } else {
+ reg14h = mdio_read(net_dev, sis_priv->cur_phy, MII_RESV);
+ if (revision == SIS630A_900_REV &&
+ (sis_priv->host_bridge_rev == SIS630B0 ||
+ sis_priv->host_bridge_rev == SIS630B1))
+ mdio_write(net_dev, sis_priv->cur_phy, MII_RESV,
+ (reg14h | 0x2200) & 0xBFFF);
+ else
+ mdio_write(net_dev, sis_priv->cur_phy, MII_RESV,
+ (reg14h | 0x2000) & 0xBFFF);
+ }
+}
+
+/**
+ * sis900_timer - sis900 timer routine
+ * @data: pointer to sis900 net device
+ *
+ * On each timer ticks we check two things,
+ * link status (ON/OFF) and link mode (10/100/Full/Half)
+ */
+
+static void sis900_timer(unsigned long data)
+{
+ struct net_device *net_dev = (struct net_device *)data;
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ struct mii_phy *mii_phy = sis_priv->mii;
+ static const int next_tick = 5*HZ;
+ u16 status;
+
+ if (!sis_priv->autong_complete){
+ int uninitialized_var(speed), duplex = 0;
+
+ sis900_read_mode(net_dev, &speed, &duplex);
+ if (duplex){
+ sis900_set_mode(net_dev->base_addr, speed, duplex);
+ sis630_set_eq(net_dev, sis_priv->chipset_rev);
+ netif_start_queue(net_dev);
+ }
+
+ sis_priv->timer.expires = jiffies + HZ;
+ add_timer(&sis_priv->timer);
+ return;
+ }
+
+ status = mdio_read(net_dev, sis_priv->cur_phy, MII_STATUS);
+ status = mdio_read(net_dev, sis_priv->cur_phy, MII_STATUS);
+
+ /* Link OFF -> ON */
+ if (!netif_carrier_ok(net_dev)) {
+ LookForLink:
+ /* Search for new PHY */
+ status = sis900_default_phy(net_dev);
+ mii_phy = sis_priv->mii;
+
+ if (status & MII_STAT_LINK){
+ sis900_check_mode(net_dev, mii_phy);
+ netif_carrier_on(net_dev);
+ }
+ } else {
+ /* Link ON -> OFF */
+ if (!(status & MII_STAT_LINK)){
+ netif_carrier_off(net_dev);
+ if(netif_msg_link(sis_priv))
+ printk(KERN_INFO "%s: Media Link Off\n", net_dev->name);
+
+ /* Change mode issue */
+ if ((mii_phy->phy_id0 == 0x001D) &&
+ ((mii_phy->phy_id1 & 0xFFF0) == 0x8000))
+ sis900_reset_phy(net_dev, sis_priv->cur_phy);
+
+ sis630_set_eq(net_dev, sis_priv->chipset_rev);
+
+ goto LookForLink;
+ }
+ }
+
+ sis_priv->timer.expires = jiffies + next_tick;
+ add_timer(&sis_priv->timer);
+}
+
+/**
+ * sis900_check_mode - check the media mode for sis900
+ * @net_dev: the net device to be checked
+ * @mii_phy: the mii phy
+ *
+ * Older driver gets the media mode from mii status output
+ * register. Now we set our media capability and auto-negotiate
+ * to get the upper bound of speed and duplex between two ends.
+ * If the types of mii phy is HOME, it doesn't need to auto-negotiate
+ * and autong_complete should be set to 1.
+ */
+
+static void sis900_check_mode(struct net_device *net_dev, struct mii_phy *mii_phy)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ long ioaddr = net_dev->base_addr;
+ int speed, duplex;
+
+ if (mii_phy->phy_types == LAN) {
+ outl(~EXD & inl(ioaddr + cfg), ioaddr + cfg);
+ sis900_set_capability(net_dev , mii_phy);
+ sis900_auto_negotiate(net_dev, sis_priv->cur_phy);
+ } else {
+ outl(EXD | inl(ioaddr + cfg), ioaddr + cfg);
+ speed = HW_SPEED_HOME;
+ duplex = FDX_CAPABLE_HALF_SELECTED;
+ sis900_set_mode(ioaddr, speed, duplex);
+ sis_priv->autong_complete = 1;
+ }
+}
+
+/**
+ * sis900_set_mode - Set the media mode of mac register.
+ * @ioaddr: the address of the device
+ * @speed : the transmit speed to be determined
+ * @duplex: the duplex mode to be determined
+ *
+ * Set the media mode of mac register txcfg/rxcfg according to
+ * speed and duplex of phy. Bit EDB_MASTER_EN indicates the EDB
+ * bus is used instead of PCI bus. When this bit is set 1, the
+ * Max DMA Burst Size for TX/RX DMA should be no larger than 16
+ * double words.
+ */
+
+static void sis900_set_mode (long ioaddr, int speed, int duplex)
+{
+ u32 tx_flags = 0, rx_flags = 0;
+
+ if (inl(ioaddr + cfg) & EDB_MASTER_EN) {
+ tx_flags = TxATP | (DMA_BURST_64 << TxMXDMA_shift) |
+ (TX_FILL_THRESH << TxFILLT_shift);
+ rx_flags = DMA_BURST_64 << RxMXDMA_shift;
+ } else {
+ tx_flags = TxATP | (DMA_BURST_512 << TxMXDMA_shift) |
+ (TX_FILL_THRESH << TxFILLT_shift);
+ rx_flags = DMA_BURST_512 << RxMXDMA_shift;
+ }
+
+ if (speed == HW_SPEED_HOME || speed == HW_SPEED_10_MBPS) {
+ rx_flags |= (RxDRNT_10 << RxDRNT_shift);
+ tx_flags |= (TxDRNT_10 << TxDRNT_shift);
+ } else {
+ rx_flags |= (RxDRNT_100 << RxDRNT_shift);
+ tx_flags |= (TxDRNT_100 << TxDRNT_shift);
+ }
+
+ if (duplex == FDX_CAPABLE_FULL_SELECTED) {
+ tx_flags |= (TxCSI | TxHBI);
+ rx_flags |= RxATX;
+ }
+
+#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
+ /* Can accept Jumbo packet */
+ rx_flags |= RxAJAB;
+#endif
+
+ outl (tx_flags, ioaddr + txcfg);
+ outl (rx_flags, ioaddr + rxcfg);
+}
+
+/**
+ * sis900_auto_negotiate - Set the Auto-Negotiation Enable/Reset bit.
+ * @net_dev: the net device to read mode for
+ * @phy_addr: mii phy address
+ *
+ * If the adapter is link-on, set the auto-negotiate enable/reset bit.
+ * autong_complete should be set to 0 when starting auto-negotiation.
+ * autong_complete should be set to 1 if we didn't start auto-negotiation.
+ * sis900_timer will wait for link on again if autong_complete = 0.
+ */
+
+static void sis900_auto_negotiate(struct net_device *net_dev, int phy_addr)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ int i = 0;
+ u32 status;
+
+ for (i = 0; i < 2; i++)
+ status = mdio_read(net_dev, phy_addr, MII_STATUS);
+
+ if (!(status & MII_STAT_LINK)){
+ if(netif_msg_link(sis_priv))
+ printk(KERN_INFO "%s: Media Link Off\n", net_dev->name);
+ sis_priv->autong_complete = 1;
+ netif_carrier_off(net_dev);
+ return;
+ }
+
+ /* (Re)start AutoNegotiate */
+ mdio_write(net_dev, phy_addr, MII_CONTROL,
+ MII_CNTL_AUTO | MII_CNTL_RST_AUTO);
+ sis_priv->autong_complete = 0;
+}
+
+
+/**
+ * sis900_read_mode - read media mode for sis900 internal phy
+ * @net_dev: the net device to read mode for
+ * @speed : the transmit speed to be determined
+ * @duplex : the duplex mode to be determined
+ *
+ * The capability of remote end will be put in mii register autorec
+ * after auto-negotiation. Use AND operation to get the upper bound
+ * of speed and duplex between two ends.
+ */
+
+static void sis900_read_mode(struct net_device *net_dev, int *speed, int *duplex)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ struct mii_phy *phy = sis_priv->mii;
+ int phy_addr = sis_priv->cur_phy;
+ u32 status;
+ u16 autoadv, autorec;
+ int i;
+
+ for (i = 0; i < 2; i++)
+ status = mdio_read(net_dev, phy_addr, MII_STATUS);
+
+ if (!(status & MII_STAT_LINK))
+ return;
+
+ /* AutoNegotiate completed */
+ autoadv = mdio_read(net_dev, phy_addr, MII_ANADV);
+ autorec = mdio_read(net_dev, phy_addr, MII_ANLPAR);
+ status = autoadv & autorec;
+
+ *speed = HW_SPEED_10_MBPS;
+ *duplex = FDX_CAPABLE_HALF_SELECTED;
+
+ if (status & (MII_NWAY_TX | MII_NWAY_TX_FDX))
+ *speed = HW_SPEED_100_MBPS;
+ if (status & ( MII_NWAY_TX_FDX | MII_NWAY_T_FDX))
+ *duplex = FDX_CAPABLE_FULL_SELECTED;
+
+ sis_priv->autong_complete = 1;
+
+ /* Workaround for Realtek RTL8201 PHY issue */
+ if ((phy->phy_id0 == 0x0000) && ((phy->phy_id1 & 0xFFF0) == 0x8200)) {
+ if (mdio_read(net_dev, phy_addr, MII_CONTROL) & MII_CNTL_FDX)
+ *duplex = FDX_CAPABLE_FULL_SELECTED;
+ if (mdio_read(net_dev, phy_addr, 0x0019) & 0x01)
+ *speed = HW_SPEED_100_MBPS;
+ }
+
+ if(netif_msg_link(sis_priv))
+ printk(KERN_INFO "%s: Media Link On %s %s-duplex\n",
+ net_dev->name,
+ *speed == HW_SPEED_100_MBPS ?
+ "100mbps" : "10mbps",
+ *duplex == FDX_CAPABLE_FULL_SELECTED ?
+ "full" : "half");
+}
+
+/**
+ * sis900_tx_timeout - sis900 transmit timeout routine
+ * @net_dev: the net device to transmit
+ *
+ * print transmit timeout status
+ * disable interrupts and do some tasks
+ */
+
+static void sis900_tx_timeout(struct net_device *net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ long ioaddr = net_dev->base_addr;
+ unsigned long flags;
+ int i;
+
+ if(netif_msg_tx_err(sis_priv))
+ printk(KERN_INFO "%s: Transmit timeout, status %8.8x %8.8x\n",
+ net_dev->name, inl(ioaddr + cr), inl(ioaddr + isr));
+
+ /* Disable interrupts by clearing the interrupt mask. */
+ outl(0x0000, ioaddr + imr);
+
+ /* use spinlock to prevent interrupt handler accessing buffer ring */
+ spin_lock_irqsave(&sis_priv->lock, flags);
+
+ /* discard unsent packets */
+ sis_priv->dirty_tx = sis_priv->cur_tx = 0;
+ for (i = 0; i < NUM_TX_DESC; i++) {
+ struct sk_buff *skb = sis_priv->tx_skbuff[i];
+
+ if (skb) {
+ pci_unmap_single(sis_priv->pci_dev,
+ sis_priv->tx_ring[i].bufptr, skb->len,
+ PCI_DMA_TODEVICE);
+ dev_kfree_skb_irq(skb);
+ sis_priv->tx_skbuff[i] = NULL;
+ sis_priv->tx_ring[i].cmdsts = 0;
+ sis_priv->tx_ring[i].bufptr = 0;
+ net_dev->stats.tx_dropped++;
+ }
+ }
+ sis_priv->tx_full = 0;
+ netif_wake_queue(net_dev);
+
+ spin_unlock_irqrestore(&sis_priv->lock, flags);
+
+ net_dev->trans_start = jiffies; /* prevent tx timeout */
+
+ /* load Transmit Descriptor Register */
+ outl(sis_priv->tx_ring_dma, ioaddr + txdp);
+
+ /* Enable all known interrupts by setting the interrupt mask. */
+ outl((RxSOVR|RxORN|RxERR|RxOK|TxURN|TxERR|TxIDLE), ioaddr + imr);
+}
+
+/**
+ * sis900_start_xmit - sis900 start transmit routine
+ * @skb: socket buffer pointer to put the data being transmitted
+ * @net_dev: the net device to transmit with
+ *
+ * Set the transmit buffer descriptor,
+ * and write TxENA to enable transmit state machine.
+ * tell upper layer if the buffer is full
+ */
+
+static netdev_tx_t
+sis900_start_xmit(struct sk_buff *skb, struct net_device *net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ long ioaddr = net_dev->base_addr;
+ unsigned int entry;
+ unsigned long flags;
+ unsigned int index_cur_tx, index_dirty_tx;
+ unsigned int count_dirty_tx;
+
+ /* Don't transmit data before the complete of auto-negotiation */
+ if(!sis_priv->autong_complete){
+ netif_stop_queue(net_dev);
+ return NETDEV_TX_BUSY;
+ }
+
+ spin_lock_irqsave(&sis_priv->lock, flags);
+
+ /* Calculate the next Tx descriptor entry. */
+ entry = sis_priv->cur_tx % NUM_TX_DESC;
+ sis_priv->tx_skbuff[entry] = skb;
+
+ /* set the transmit buffer descriptor and enable Transmit State Machine */
+ sis_priv->tx_ring[entry].bufptr = pci_map_single(sis_priv->pci_dev,
+ skb->data, skb->len, PCI_DMA_TODEVICE);
+ sis_priv->tx_ring[entry].cmdsts = (OWN | skb->len);
+ outl(TxENA | inl(ioaddr + cr), ioaddr + cr);
+
+ sis_priv->cur_tx ++;
+ index_cur_tx = sis_priv->cur_tx;
+ index_dirty_tx = sis_priv->dirty_tx;
+
+ for (count_dirty_tx = 0; index_cur_tx != index_dirty_tx; index_dirty_tx++)
+ count_dirty_tx ++;
+
+ if (index_cur_tx == index_dirty_tx) {
+ /* dirty_tx is met in the cycle of cur_tx, buffer full */
+ sis_priv->tx_full = 1;
+ netif_stop_queue(net_dev);
+ } else if (count_dirty_tx < NUM_TX_DESC) {
+ /* Typical path, tell upper layer that more transmission is possible */
+ netif_start_queue(net_dev);
+ } else {
+ /* buffer full, tell upper layer no more transmission */
+ sis_priv->tx_full = 1;
+ netif_stop_queue(net_dev);
+ }
+
+ spin_unlock_irqrestore(&sis_priv->lock, flags);
+
+ if (netif_msg_tx_queued(sis_priv))
+ printk(KERN_DEBUG "%s: Queued Tx packet at %p size %d "
+ "to slot %d.\n",
+ net_dev->name, skb->data, (int)skb->len, entry);
+
+ return NETDEV_TX_OK;
+}
+
+/**
+ * sis900_interrupt - sis900 interrupt handler
+ * @irq: the irq number
+ * @dev_instance: the client data object
+ *
+ * The interrupt handler does all of the Rx thread work,
+ * and cleans up after the Tx thread
+ */
+
+static irqreturn_t sis900_interrupt(int irq, void *dev_instance)
+{
+ struct net_device *net_dev = dev_instance;
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ int boguscnt = max_interrupt_work;
+ long ioaddr = net_dev->base_addr;
+ u32 status;
+ unsigned int handled = 0;
+
+ spin_lock (&sis_priv->lock);
+
+ do {
+ status = inl(ioaddr + isr);
+
+ if ((status & (HIBERR|TxURN|TxERR|TxIDLE|RxORN|RxERR|RxOK)) == 0)
+ /* nothing intresting happened */
+ break;
+ handled = 1;
+
+ /* why dow't we break after Tx/Rx case ?? keyword: full-duplex */
+ if (status & (RxORN | RxERR | RxOK))
+ /* Rx interrupt */
+ sis900_rx(net_dev);
+
+ if (status & (TxURN | TxERR | TxIDLE))
+ /* Tx interrupt */
+ sis900_finish_xmit(net_dev);
+
+ /* something strange happened !!! */
+ if (status & HIBERR) {
+ if(netif_msg_intr(sis_priv))
+ printk(KERN_INFO "%s: Abnormal interrupt, "
+ "status %#8.8x.\n", net_dev->name, status);
+ break;
+ }
+ if (--boguscnt < 0) {
+ if(netif_msg_intr(sis_priv))
+ printk(KERN_INFO "%s: Too much work at interrupt, "
+ "interrupt status = %#8.8x.\n",
+ net_dev->name, status);
+ break;
+ }
+ } while (1);
+
+ if(netif_msg_intr(sis_priv))
+ printk(KERN_DEBUG "%s: exiting interrupt, "
+ "interrupt status = 0x%#8.8x.\n",
+ net_dev->name, inl(ioaddr + isr));
+
+ spin_unlock (&sis_priv->lock);
+ return IRQ_RETVAL(handled);
+}
+
+/**
+ * sis900_rx - sis900 receive routine
+ * @net_dev: the net device which receives data
+ *
+ * Process receive interrupt events,
+ * put buffer to higher layer and refill buffer pool
+ * Note: This function is called by interrupt handler,
+ * don't do "too much" work here
+ */
+
+static int sis900_rx(struct net_device *net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ long ioaddr = net_dev->base_addr;
+ unsigned int entry = sis_priv->cur_rx % NUM_RX_DESC;
+ u32 rx_status = sis_priv->rx_ring[entry].cmdsts;
+ int rx_work_limit;
+
+ if (netif_msg_rx_status(sis_priv))
+ printk(KERN_DEBUG "sis900_rx, cur_rx:%4.4d, dirty_rx:%4.4d "
+ "status:0x%8.8x\n",
+ sis_priv->cur_rx, sis_priv->dirty_rx, rx_status);
+ rx_work_limit = sis_priv->dirty_rx + NUM_RX_DESC - sis_priv->cur_rx;
+
+ while (rx_status & OWN) {
+ unsigned int rx_size;
+ unsigned int data_size;
+
+ if (--rx_work_limit < 0)
+ break;
+
+ data_size = rx_status & DSIZE;
+ rx_size = data_size - CRC_SIZE;
+
+#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
+ /* ``TOOLONG'' flag means jumbo packet received. */
+ if ((rx_status & TOOLONG) && data_size <= MAX_FRAME_SIZE)
+ rx_status &= (~ ((unsigned int)TOOLONG));
+#endif
+
+ if (rx_status & (ABORT|OVERRUN|TOOLONG|RUNT|RXISERR|CRCERR|FAERR)) {
+ /* corrupted packet received */
+ if (netif_msg_rx_err(sis_priv))
+ printk(KERN_DEBUG "%s: Corrupted packet "
+ "received, buffer status = 0x%8.8x/%d.\n",
+ net_dev->name, rx_status, data_size);
+ net_dev->stats.rx_errors++;
+ if (rx_status & OVERRUN)
+ net_dev->stats.rx_over_errors++;
+ if (rx_status & (TOOLONG|RUNT))
+ net_dev->stats.rx_length_errors++;
+ if (rx_status & (RXISERR | FAERR))
+ net_dev->stats.rx_frame_errors++;
+ if (rx_status & CRCERR)
+ net_dev->stats.rx_crc_errors++;
+ /* reset buffer descriptor state */
+ sis_priv->rx_ring[entry].cmdsts = RX_BUF_SIZE;
+ } else {
+ struct sk_buff * skb;
+ struct sk_buff * rx_skb;
+
+ pci_unmap_single(sis_priv->pci_dev,
+ sis_priv->rx_ring[entry].bufptr, RX_BUF_SIZE,
+ PCI_DMA_FROMDEVICE);
+
+ /* refill the Rx buffer, what if there is not enough
+ * memory for new socket buffer ?? */
+ if ((skb = dev_alloc_skb(RX_BUF_SIZE)) == NULL) {
+ /*
+ * Not enough memory to refill the buffer
+ * so we need to recycle the old one so
+ * as to avoid creating a memory hole
+ * in the rx ring
+ */
+ skb = sis_priv->rx_skbuff[entry];
+ net_dev->stats.rx_dropped++;
+ goto refill_rx_ring;
+ }
+
+ /* This situation should never happen, but due to
+ some unknown bugs, it is possible that
+ we are working on NULL sk_buff :-( */
+ if (sis_priv->rx_skbuff[entry] == NULL) {
+ if (netif_msg_rx_err(sis_priv))
+ printk(KERN_WARNING "%s: NULL pointer "
+ "encountered in Rx ring\n"
+ "cur_rx:%4.4d, dirty_rx:%4.4d\n",
+ net_dev->name, sis_priv->cur_rx,
+ sis_priv->dirty_rx);
+ dev_kfree_skb(skb);
+ break;
+ }
+
+ /* give the socket buffer to upper layers */
+ rx_skb = sis_priv->rx_skbuff[entry];
+ skb_put(rx_skb, rx_size);
+ rx_skb->protocol = eth_type_trans(rx_skb, net_dev);
+ netif_rx(rx_skb);
+
+ /* some network statistics */
+ if ((rx_status & BCAST) == MCAST)
+ net_dev->stats.multicast++;
+ net_dev->stats.rx_bytes += rx_size;
+ net_dev->stats.rx_packets++;
+ sis_priv->dirty_rx++;
+refill_rx_ring:
+ sis_priv->rx_skbuff[entry] = skb;
+ sis_priv->rx_ring[entry].cmdsts = RX_BUF_SIZE;
+ sis_priv->rx_ring[entry].bufptr =
+ pci_map_single(sis_priv->pci_dev, skb->data,
+ RX_BUF_SIZE, PCI_DMA_FROMDEVICE);
+ }
+ sis_priv->cur_rx++;
+ entry = sis_priv->cur_rx % NUM_RX_DESC;
+ rx_status = sis_priv->rx_ring[entry].cmdsts;
+ } // while
+
+ /* refill the Rx buffer, what if the rate of refilling is slower
+ * than consuming ?? */
+ for (; sis_priv->cur_rx != sis_priv->dirty_rx; sis_priv->dirty_rx++) {
+ struct sk_buff *skb;
+
+ entry = sis_priv->dirty_rx % NUM_RX_DESC;
+
+ if (sis_priv->rx_skbuff[entry] == NULL) {
+ if ((skb = dev_alloc_skb(RX_BUF_SIZE)) == NULL) {
+ /* not enough memory for skbuff, this makes a
+ * "hole" on the buffer ring, it is not clear
+ * how the hardware will react to this kind
+ * of degenerated buffer */
+ if (netif_msg_rx_err(sis_priv))
+ printk(KERN_INFO "%s: Memory squeeze, "
+ "deferring packet.\n",
+ net_dev->name);
+ net_dev->stats.rx_dropped++;
+ break;
+ }
+ sis_priv->rx_skbuff[entry] = skb;
+ sis_priv->rx_ring[entry].cmdsts = RX_BUF_SIZE;
+ sis_priv->rx_ring[entry].bufptr =
+ pci_map_single(sis_priv->pci_dev, skb->data,
+ RX_BUF_SIZE, PCI_DMA_FROMDEVICE);
+ }
+ }
+ /* re-enable the potentially idle receive state matchine */
+ outl(RxENA | inl(ioaddr + cr), ioaddr + cr );
+
+ return 0;
+}
+
+/**
+ * sis900_finish_xmit - finish up transmission of packets
+ * @net_dev: the net device to be transmitted on
+ *
+ * Check for error condition and free socket buffer etc
+ * schedule for more transmission as needed
+ * Note: This function is called by interrupt handler,
+ * don't do "too much" work here
+ */
+
+static void sis900_finish_xmit (struct net_device *net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+
+ for (; sis_priv->dirty_tx != sis_priv->cur_tx; sis_priv->dirty_tx++) {
+ struct sk_buff *skb;
+ unsigned int entry;
+ u32 tx_status;
+
+ entry = sis_priv->dirty_tx % NUM_TX_DESC;
+ tx_status = sis_priv->tx_ring[entry].cmdsts;
+
+ if (tx_status & OWN) {
+ /* The packet is not transmitted yet (owned by hardware) !
+ * Note: the interrupt is generated only when Tx Machine
+ * is idle, so this is an almost impossible case */
+ break;
+ }
+
+ if (tx_status & (ABORT | UNDERRUN | OWCOLL)) {
+ /* packet unsuccessfully transmitted */
+ if (netif_msg_tx_err(sis_priv))
+ printk(KERN_DEBUG "%s: Transmit "
+ "error, Tx status %8.8x.\n",
+ net_dev->name, tx_status);
+ net_dev->stats.tx_errors++;
+ if (tx_status & UNDERRUN)
+ net_dev->stats.tx_fifo_errors++;
+ if (tx_status & ABORT)
+ net_dev->stats.tx_aborted_errors++;
+ if (tx_status & NOCARRIER)
+ net_dev->stats.tx_carrier_errors++;
+ if (tx_status & OWCOLL)
+ net_dev->stats.tx_window_errors++;
+ } else {
+ /* packet successfully transmitted */
+ net_dev->stats.collisions += (tx_status & COLCNT) >> 16;
+ net_dev->stats.tx_bytes += tx_status & DSIZE;
+ net_dev->stats.tx_packets++;
+ }
+ /* Free the original skb. */
+ skb = sis_priv->tx_skbuff[entry];
+ pci_unmap_single(sis_priv->pci_dev,
+ sis_priv->tx_ring[entry].bufptr, skb->len,
+ PCI_DMA_TODEVICE);
+ dev_kfree_skb_irq(skb);
+ sis_priv->tx_skbuff[entry] = NULL;
+ sis_priv->tx_ring[entry].bufptr = 0;
+ sis_priv->tx_ring[entry].cmdsts = 0;
+ }
+
+ if (sis_priv->tx_full && netif_queue_stopped(net_dev) &&
+ sis_priv->cur_tx - sis_priv->dirty_tx < NUM_TX_DESC - 4) {
+ /* The ring is no longer full, clear tx_full and schedule
+ * more transmission by netif_wake_queue(net_dev) */
+ sis_priv->tx_full = 0;
+ netif_wake_queue (net_dev);
+ }
+}
+
+/**
+ * sis900_close - close sis900 device
+ * @net_dev: the net device to be closed
+ *
+ * Disable interrupts, stop the Tx and Rx Status Machine
+ * free Tx and RX socket buffer
+ */
+
+static int sis900_close(struct net_device *net_dev)
+{
+ long ioaddr = net_dev->base_addr;
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ struct sk_buff *skb;
+ int i;
+
+ netif_stop_queue(net_dev);
+
+ /* Disable interrupts by clearing the interrupt mask. */
+ outl(0x0000, ioaddr + imr);
+ outl(0x0000, ioaddr + ier);
+
+ /* Stop the chip's Tx and Rx Status Machine */
+ outl(RxDIS | TxDIS | inl(ioaddr + cr), ioaddr + cr);
+
+ del_timer(&sis_priv->timer);
+
+ free_irq(net_dev->irq, net_dev);
+
+ /* Free Tx and RX skbuff */
+ for (i = 0; i < NUM_RX_DESC; i++) {
+ skb = sis_priv->rx_skbuff[i];
+ if (skb) {
+ pci_unmap_single(sis_priv->pci_dev,
+ sis_priv->rx_ring[i].bufptr,
+ RX_BUF_SIZE, PCI_DMA_FROMDEVICE);
+ dev_kfree_skb(skb);
+ sis_priv->rx_skbuff[i] = NULL;
+ }
+ }
+ for (i = 0; i < NUM_TX_DESC; i++) {
+ skb = sis_priv->tx_skbuff[i];
+ if (skb) {
+ pci_unmap_single(sis_priv->pci_dev,
+ sis_priv->tx_ring[i].bufptr, skb->len,
+ PCI_DMA_TODEVICE);
+ dev_kfree_skb(skb);
+ sis_priv->tx_skbuff[i] = NULL;
+ }
+ }
+
+ /* Green! Put the chip in low-power mode. */
+
+ return 0;
+}
+
+/**
+ * sis900_get_drvinfo - Return information about driver
+ * @net_dev: the net device to probe
+ * @info: container for info returned
+ *
+ * Process ethtool command such as "ehtool -i" to show information
+ */
+
+static void sis900_get_drvinfo(struct net_device *net_dev,
+ struct ethtool_drvinfo *info)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+
+ strcpy (info->driver, SIS900_MODULE_NAME);
+ strcpy (info->version, SIS900_DRV_VERSION);
+ strcpy (info->bus_info, pci_name(sis_priv->pci_dev));
+}
+
+static u32 sis900_get_msglevel(struct net_device *net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ return sis_priv->msg_enable;
+}
+
+static void sis900_set_msglevel(struct net_device *net_dev, u32 value)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ sis_priv->msg_enable = value;
+}
+
+static u32 sis900_get_link(struct net_device *net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ return mii_link_ok(&sis_priv->mii_info);
+}
+
+static int sis900_get_settings(struct net_device *net_dev,
+ struct ethtool_cmd *cmd)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ spin_lock_irq(&sis_priv->lock);
+ mii_ethtool_gset(&sis_priv->mii_info, cmd);
+ spin_unlock_irq(&sis_priv->lock);
+ return 0;
+}
+
+static int sis900_set_settings(struct net_device *net_dev,
+ struct ethtool_cmd *cmd)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ int rt;
+ spin_lock_irq(&sis_priv->lock);
+ rt = mii_ethtool_sset(&sis_priv->mii_info, cmd);
+ spin_unlock_irq(&sis_priv->lock);
+ return rt;
+}
+
+static int sis900_nway_reset(struct net_device *net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ return mii_nway_restart(&sis_priv->mii_info);
+}
+
+/**
+ * sis900_set_wol - Set up Wake on Lan registers
+ * @net_dev: the net device to probe
+ * @wol: container for info passed to the driver
+ *
+ * Process ethtool command "wol" to setup wake on lan features.
+ * SiS900 supports sending WoL events if a correct packet is received,
+ * but there is no simple way to filter them to only a subset (broadcast,
+ * multicast, unicast or arp).
+ */
+
+static int sis900_set_wol(struct net_device *net_dev, struct ethtool_wolinfo *wol)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ long pmctrl_addr = net_dev->base_addr + pmctrl;
+ u32 cfgpmcsr = 0, pmctrl_bits = 0;
+
+ if (wol->wolopts == 0) {
+ pci_read_config_dword(sis_priv->pci_dev, CFGPMCSR, &cfgpmcsr);
+ cfgpmcsr &= ~PME_EN;
+ pci_write_config_dword(sis_priv->pci_dev, CFGPMCSR, cfgpmcsr);
+ outl(pmctrl_bits, pmctrl_addr);
+ if (netif_msg_wol(sis_priv))
+ printk(KERN_DEBUG "%s: Wake on LAN disabled\n", net_dev->name);
+ return 0;
+ }
+
+ if (wol->wolopts & (WAKE_MAGICSECURE | WAKE_UCAST | WAKE_MCAST
+ | WAKE_BCAST | WAKE_ARP))
+ return -EINVAL;
+
+ if (wol->wolopts & WAKE_MAGIC)
+ pmctrl_bits |= MAGICPKT;
+ if (wol->wolopts & WAKE_PHY)
+ pmctrl_bits |= LINKON;
+
+ outl(pmctrl_bits, pmctrl_addr);
+
+ pci_read_config_dword(sis_priv->pci_dev, CFGPMCSR, &cfgpmcsr);
+ cfgpmcsr |= PME_EN;
+ pci_write_config_dword(sis_priv->pci_dev, CFGPMCSR, cfgpmcsr);
+ if (netif_msg_wol(sis_priv))
+ printk(KERN_DEBUG "%s: Wake on LAN enabled\n", net_dev->name);
+
+ return 0;
+}
+
+static void sis900_get_wol(struct net_device *net_dev, struct ethtool_wolinfo *wol)
+{
+ long pmctrl_addr = net_dev->base_addr + pmctrl;
+ u32 pmctrl_bits;
+
+ pmctrl_bits = inl(pmctrl_addr);
+ if (pmctrl_bits & MAGICPKT)
+ wol->wolopts |= WAKE_MAGIC;
+ if (pmctrl_bits & LINKON)
+ wol->wolopts |= WAKE_PHY;
+
+ wol->supported = (WAKE_PHY | WAKE_MAGIC);
+}
+
+static const struct ethtool_ops sis900_ethtool_ops = {
+ .get_drvinfo = sis900_get_drvinfo,
+ .get_msglevel = sis900_get_msglevel,
+ .set_msglevel = sis900_set_msglevel,
+ .get_link = sis900_get_link,
+ .get_settings = sis900_get_settings,
+ .set_settings = sis900_set_settings,
+ .nway_reset = sis900_nway_reset,
+ .get_wol = sis900_get_wol,
+ .set_wol = sis900_set_wol
+};
+
+/**
+ * mii_ioctl - process MII i/o control command
+ * @net_dev: the net device to command for
+ * @rq: parameter for command
+ * @cmd: the i/o command
+ *
+ * Process MII command like read/write MII register
+ */
+
+static int mii_ioctl(struct net_device *net_dev, struct ifreq *rq, int cmd)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ struct mii_ioctl_data *data = if_mii(rq);
+
+ switch(cmd) {
+ case SIOCGMIIPHY: /* Get address of MII PHY in use. */
+ data->phy_id = sis_priv->mii->phy_addr;
+ /* Fall Through */
+
+ case SIOCGMIIREG: /* Read MII PHY register. */
+ data->val_out = mdio_read(net_dev, data->phy_id & 0x1f, data->reg_num & 0x1f);
+ return 0;
+
+ case SIOCSMIIREG: /* Write MII PHY register. */
+ mdio_write(net_dev, data->phy_id & 0x1f, data->reg_num & 0x1f, data->val_in);
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/**
+ * sis900_set_config - Set media type by net_device.set_config
+ * @dev: the net device for media type change
+ * @map: ifmap passed by ifconfig
+ *
+ * Set media type to 10baseT, 100baseT or 0(for auto) by ifconfig
+ * we support only port changes. All other runtime configuration
+ * changes will be ignored
+ */
+
+static int sis900_set_config(struct net_device *dev, struct ifmap *map)
+{
+ struct sis900_private *sis_priv = netdev_priv(dev);
+ struct mii_phy *mii_phy = sis_priv->mii;
+
+ u16 status;
+
+ if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) {
+ /* we switch on the ifmap->port field. I couldn't find anything
+ * like a definition or standard for the values of that field.
+ * I think the meaning of those values is device specific. But
+ * since I would like to change the media type via the ifconfig
+ * command I use the definition from linux/netdevice.h
+ * (which seems to be different from the ifport(pcmcia) definition) */
+ switch(map->port){
+ case IF_PORT_UNKNOWN: /* use auto here */
+ dev->if_port = map->port;
+ /* we are going to change the media type, so the Link
+ * will be temporary down and we need to reflect that
+ * here. When the Link comes up again, it will be
+ * sensed by the sis_timer procedure, which also does
+ * all the rest for us */
+ netif_carrier_off(dev);
+
+ /* read current state */
+ status = mdio_read(dev, mii_phy->phy_addr, MII_CONTROL);
+
+ /* enable auto negotiation and reset the negotioation
+ * (I don't really know what the auto negatiotiation
+ * reset really means, but it sounds for me right to
+ * do one here) */
+ mdio_write(dev, mii_phy->phy_addr,
+ MII_CONTROL, status | MII_CNTL_AUTO | MII_CNTL_RST_AUTO);
+
+ break;
+
+ case IF_PORT_10BASET: /* 10BaseT */
+ dev->if_port = map->port;
+
+ /* we are going to change the media type, so the Link
+ * will be temporary down and we need to reflect that
+ * here. When the Link comes up again, it will be
+ * sensed by the sis_timer procedure, which also does
+ * all the rest for us */
+ netif_carrier_off(dev);
+
+ /* set Speed to 10Mbps */
+ /* read current state */
+ status = mdio_read(dev, mii_phy->phy_addr, MII_CONTROL);
+
+ /* disable auto negotiation and force 10MBit mode*/
+ mdio_write(dev, mii_phy->phy_addr,
+ MII_CONTROL, status & ~(MII_CNTL_SPEED |
+ MII_CNTL_AUTO));
+ break;
+
+ case IF_PORT_100BASET: /* 100BaseT */
+ case IF_PORT_100BASETX: /* 100BaseTx */
+ dev->if_port = map->port;
+
+ /* we are going to change the media type, so the Link
+ * will be temporary down and we need to reflect that
+ * here. When the Link comes up again, it will be
+ * sensed by the sis_timer procedure, which also does
+ * all the rest for us */
+ netif_carrier_off(dev);
+
+ /* set Speed to 100Mbps */
+ /* disable auto negotiation and enable 100MBit Mode */
+ status = mdio_read(dev, mii_phy->phy_addr, MII_CONTROL);
+ mdio_write(dev, mii_phy->phy_addr,
+ MII_CONTROL, (status & ~MII_CNTL_SPEED) |
+ MII_CNTL_SPEED);
+
+ break;
+
+ case IF_PORT_10BASE2: /* 10Base2 */
+ case IF_PORT_AUI: /* AUI */
+ case IF_PORT_100BASEFX: /* 100BaseFx */
+ /* These Modes are not supported (are they?)*/
+ return -EOPNOTSUPP;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+/**
+ * sis900_mcast_bitnr - compute hashtable index
+ * @addr: multicast address
+ * @revision: revision id of chip
+ *
+ * SiS 900 uses the most sigificant 7 bits to index a 128 bits multicast
+ * hash table, which makes this function a little bit different from other drivers
+ * SiS 900 B0 & 635 M/B uses the most significat 8 bits to index 256 bits
+ * multicast hash table.
+ */
+
+static inline u16 sis900_mcast_bitnr(u8 *addr, u8 revision)
+{
+
+ u32 crc = ether_crc(6, addr);
+
+ /* leave 8 or 7 most siginifant bits */
+ if ((revision >= SIS635A_900_REV) || (revision == SIS900B_900_REV))
+ return (int)(crc >> 24);
+ else
+ return (int)(crc >> 25);
+}
+
+/**
+ * set_rx_mode - Set SiS900 receive mode
+ * @net_dev: the net device to be set
+ *
+ * Set SiS900 receive mode for promiscuous, multicast, or broadcast mode.
+ * And set the appropriate multicast filter.
+ * Multicast hash table changes from 128 to 256 bits for 635M/B & 900B0.
+ */
+
+static void set_rx_mode(struct net_device *net_dev)
+{
+ long ioaddr = net_dev->base_addr;
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ u16 mc_filter[16] = {0}; /* 256/128 bits multicast hash table */
+ int i, table_entries;
+ u32 rx_mode;
+
+ /* 635 Hash Table entries = 256(2^16) */
+ if((sis_priv->chipset_rev >= SIS635A_900_REV) ||
+ (sis_priv->chipset_rev == SIS900B_900_REV))
+ table_entries = 16;
+ else
+ table_entries = 8;
+
+ if (net_dev->flags & IFF_PROMISC) {
+ /* Accept any kinds of packets */
+ rx_mode = RFPromiscuous;
+ for (i = 0; i < table_entries; i++)
+ mc_filter[i] = 0xffff;
+ } else if ((netdev_mc_count(net_dev) > multicast_filter_limit) ||
+ (net_dev->flags & IFF_ALLMULTI)) {
+ /* too many multicast addresses or accept all multicast packet */
+ rx_mode = RFAAB | RFAAM;
+ for (i = 0; i < table_entries; i++)
+ mc_filter[i] = 0xffff;
+ } else {
+ /* Accept Broadcast packet, destination address matchs our
+ * MAC address, use Receive Filter to reject unwanted MCAST
+ * packets */
+ struct netdev_hw_addr *ha;
+ rx_mode = RFAAB;
+
+ netdev_for_each_mc_addr(ha, net_dev) {
+ unsigned int bit_nr;
+
+ bit_nr = sis900_mcast_bitnr(ha->addr,
+ sis_priv->chipset_rev);
+ mc_filter[bit_nr >> 4] |= (1 << (bit_nr & 0xf));
+ }
+ }
+
+ /* update Multicast Hash Table in Receive Filter */
+ for (i = 0; i < table_entries; i++) {
+ /* why plus 0x04 ??, That makes the correct value for hash table. */
+ outl((u32)(0x00000004+i) << RFADDR_shift, ioaddr + rfcr);
+ outl(mc_filter[i], ioaddr + rfdr);
+ }
+
+ outl(RFEN | rx_mode, ioaddr + rfcr);
+
+ /* sis900 is capable of looping back packets at MAC level for
+ * debugging purpose */
+ if (net_dev->flags & IFF_LOOPBACK) {
+ u32 cr_saved;
+ /* We must disable Tx/Rx before setting loopback mode */
+ cr_saved = inl(ioaddr + cr);
+ outl(cr_saved | TxDIS | RxDIS, ioaddr + cr);
+ /* enable loopback */
+ outl(inl(ioaddr + txcfg) | TxMLB, ioaddr + txcfg);
+ outl(inl(ioaddr + rxcfg) | RxATX, ioaddr + rxcfg);
+ /* restore cr */
+ outl(cr_saved, ioaddr + cr);
+ }
+}
+
+/**
+ * sis900_reset - Reset sis900 MAC
+ * @net_dev: the net device to reset
+ *
+ * reset sis900 MAC and wait until finished
+ * reset through command register
+ * change backoff algorithm for 900B0 & 635 M/B
+ */
+
+static void sis900_reset(struct net_device *net_dev)
+{
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ long ioaddr = net_dev->base_addr;
+ int i = 0;
+ u32 status = TxRCMP | RxRCMP;
+
+ outl(0, ioaddr + ier);
+ outl(0, ioaddr + imr);
+ outl(0, ioaddr + rfcr);
+
+ outl(RxRESET | TxRESET | RESET | inl(ioaddr + cr), ioaddr + cr);
+
+ /* Check that the chip has finished the reset. */
+ while (status && (i++ < 1000)) {
+ status ^= (inl(isr + ioaddr) & status);
+ }
+
+ if( (sis_priv->chipset_rev >= SIS635A_900_REV) ||
+ (sis_priv->chipset_rev == SIS900B_900_REV) )
+ outl(PESEL | RND_CNT, ioaddr + cfg);
+ else
+ outl(PESEL, ioaddr + cfg);
+}
+
+/**
+ * sis900_remove - Remove sis900 device
+ * @pci_dev: the pci device to be removed
+ *
+ * remove and release SiS900 net device
+ */
+
+static void __devexit sis900_remove(struct pci_dev *pci_dev)
+{
+ struct net_device *net_dev = pci_get_drvdata(pci_dev);
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ struct mii_phy *phy = NULL;
+
+ while (sis_priv->first_mii) {
+ phy = sis_priv->first_mii;
+ sis_priv->first_mii = phy->next;
+ kfree(phy);
+ }
+
+ pci_free_consistent(pci_dev, RX_TOTAL_SIZE, sis_priv->rx_ring,
+ sis_priv->rx_ring_dma);
+ pci_free_consistent(pci_dev, TX_TOTAL_SIZE, sis_priv->tx_ring,
+ sis_priv->tx_ring_dma);
+ unregister_netdev(net_dev);
+ free_netdev(net_dev);
+ pci_release_regions(pci_dev);
+ pci_set_drvdata(pci_dev, NULL);
+}
+
+#ifdef CONFIG_PM
+
+static int sis900_suspend(struct pci_dev *pci_dev, pm_message_t state)
+{
+ struct net_device *net_dev = pci_get_drvdata(pci_dev);
+ long ioaddr = net_dev->base_addr;
+
+ if(!netif_running(net_dev))
+ return 0;
+
+ netif_stop_queue(net_dev);
+ netif_device_detach(net_dev);
+
+ /* Stop the chip's Tx and Rx Status Machine */
+ outl(RxDIS | TxDIS | inl(ioaddr + cr), ioaddr + cr);
+
+ pci_set_power_state(pci_dev, PCI_D3hot);
+ pci_save_state(pci_dev);
+
+ return 0;
+}
+
+static int sis900_resume(struct pci_dev *pci_dev)
+{
+ struct net_device *net_dev = pci_get_drvdata(pci_dev);
+ struct sis900_private *sis_priv = netdev_priv(net_dev);
+ long ioaddr = net_dev->base_addr;
+
+ if(!netif_running(net_dev))
+ return 0;
+ pci_restore_state(pci_dev);
+ pci_set_power_state(pci_dev, PCI_D0);
+
+ sis900_init_rxfilter(net_dev);
+
+ sis900_init_tx_ring(net_dev);
+ sis900_init_rx_ring(net_dev);
+
+ set_rx_mode(net_dev);
+
+ netif_device_attach(net_dev);
+ netif_start_queue(net_dev);
+
+ /* Workaround for EDB */
+ sis900_set_mode(ioaddr, HW_SPEED_10_MBPS, FDX_CAPABLE_HALF_SELECTED);
+
+ /* Enable all known interrupts by setting the interrupt mask. */
+ outl((RxSOVR|RxORN|RxERR|RxOK|TxURN|TxERR|TxIDLE), ioaddr + imr);
+ outl(RxENA | inl(ioaddr + cr), ioaddr + cr);
+ outl(IE, ioaddr + ier);
+
+ sis900_check_mode(net_dev, sis_priv->mii);
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static struct pci_driver sis900_pci_driver = {
+ .name = SIS900_MODULE_NAME,
+ .id_table = sis900_pci_tbl,
+ .probe = sis900_probe,
+ .remove = __devexit_p(sis900_remove),
+#ifdef CONFIG_PM
+ .suspend = sis900_suspend,
+ .resume = sis900_resume,
+#endif /* CONFIG_PM */
+};
+
+static int __init sis900_init_module(void)
+{
+/* when a module, this is printed whether or not devices are found in probe */
+#ifdef MODULE
+ printk(version);
+#endif
+
+ return pci_register_driver(&sis900_pci_driver);
+}
+
+static void __exit sis900_cleanup_module(void)
+{
+ pci_unregister_driver(&sis900_pci_driver);
+}
+
+module_init(sis900_init_module);
+module_exit(sis900_cleanup_module);
+
diff --git a/drivers/net/ethernet/sis/sis900.h b/drivers/net/ethernet/sis/sis900.h
new file mode 100644
index 000000000000..150511a922ef
--- /dev/null
+++ b/drivers/net/ethernet/sis/sis900.h
@@ -0,0 +1,329 @@
+/* sis900.h Definitions for SiS ethernet controllers including 7014/7016 and 900
+ * Copyright 1999 Silicon Integrated System Corporation
+ * References:
+ * SiS 7016 Fast Ethernet PCI Bus 10/100 Mbps LAN Controller with OnNow Support,
+ * preliminary Rev. 1.0 Jan. 14, 1998
+ * SiS 900 Fast Ethernet PCI Bus 10/100 Mbps LAN Single Chip with OnNow Support,
+ * preliminary Rev. 1.0 Nov. 10, 1998
+ * SiS 7014 Single Chip 100BASE-TX/10BASE-T Physical Layer Solution,
+ * preliminary Rev. 1.0 Jan. 18, 1998
+ * http://www.sis.com.tw/support/databook.htm
+ */
+
+/*
+ * SiS 7016 and SiS 900 ethernet controller registers
+ */
+
+/* The I/O extent, SiS 900 needs 256 bytes of io address */
+#define SIS900_TOTAL_SIZE 0x100
+
+/* Symbolic offsets to registers. */
+enum sis900_registers {
+ cr=0x0, //Command Register
+ cfg=0x4, //Configuration Register
+ mear=0x8, //EEPROM Access Register
+ ptscr=0xc, //PCI Test Control Register
+ isr=0x10, //Interrupt Status Register
+ imr=0x14, //Interrupt Mask Register
+ ier=0x18, //Interrupt Enable Register
+ epar=0x18, //Enhanced PHY Access Register
+ txdp=0x20, //Transmit Descriptor Pointer Register
+ txcfg=0x24, //Transmit Configuration Register
+ rxdp=0x30, //Receive Descriptor Pointer Register
+ rxcfg=0x34, //Receive Configuration Register
+ flctrl=0x38, //Flow Control Register
+ rxlen=0x3c, //Receive Packet Length Register
+ rfcr=0x48, //Receive Filter Control Register
+ rfdr=0x4C, //Receive Filter Data Register
+ pmctrl=0xB0, //Power Management Control Register
+ pmer=0xB4 //Power Management Wake-up Event Register
+};
+
+/* Symbolic names for bits in various registers */
+enum sis900_command_register_bits {
+ RELOAD = 0x00000400, ACCESSMODE = 0x00000200,/* ET */
+ RESET = 0x00000100, SWI = 0x00000080, RxRESET = 0x00000020,
+ TxRESET = 0x00000010, RxDIS = 0x00000008, RxENA = 0x00000004,
+ TxDIS = 0x00000002, TxENA = 0x00000001
+};
+
+enum sis900_configuration_register_bits {
+ DESCRFMT = 0x00000100 /* 7016 specific */, REQALG = 0x00000080,
+ SB = 0x00000040, POW = 0x00000020, EXD = 0x00000010,
+ PESEL = 0x00000008, LPM = 0x00000004, BEM = 0x00000001,
+ /* 635 & 900B Specific */
+ RND_CNT = 0x00000400, FAIR_BACKOFF = 0x00000200,
+ EDB_MASTER_EN = 0x00002000
+};
+
+enum sis900_eeprom_access_reigster_bits {
+ MDC = 0x00000040, MDDIR = 0x00000020, MDIO = 0x00000010, /* 7016 specific */
+ EECS = 0x00000008, EECLK = 0x00000004, EEDO = 0x00000002,
+ EEDI = 0x00000001
+};
+
+enum sis900_interrupt_register_bits {
+ WKEVT = 0x10000000, TxPAUSEEND = 0x08000000, TxPAUSE = 0x04000000,
+ TxRCMP = 0x02000000, RxRCMP = 0x01000000, DPERR = 0x00800000,
+ SSERR = 0x00400000, RMABT = 0x00200000, RTABT = 0x00100000,
+ RxSOVR = 0x00010000, HIBERR = 0x00008000, SWINT = 0x00001000,
+ MIBINT = 0x00000800, TxURN = 0x00000400, TxIDLE = 0x00000200,
+ TxERR = 0x00000100, TxDESC = 0x00000080, TxOK = 0x00000040,
+ RxORN = 0x00000020, RxIDLE = 0x00000010, RxEARLY = 0x00000008,
+ RxERR = 0x00000004, RxDESC = 0x00000002, RxOK = 0x00000001
+};
+
+enum sis900_interrupt_enable_reigster_bits {
+ IE = 0x00000001
+};
+
+/* maximum dma burst for transmission and receive */
+#define MAX_DMA_RANGE 7 /* actually 0 means MAXIMUM !! */
+#define TxMXDMA_shift 20
+#define RxMXDMA_shift 20
+
+enum sis900_tx_rx_dma{
+ DMA_BURST_512 = 0, DMA_BURST_64 = 5
+};
+
+/* transmit FIFO thresholds */
+#define TX_FILL_THRESH 16 /* 1/4 FIFO size */
+#define TxFILLT_shift 8
+#define TxDRNT_shift 0
+#define TxDRNT_100 48 /* 3/4 FIFO size */
+#define TxDRNT_10 16 /* 1/2 FIFO size */
+
+enum sis900_transmit_config_register_bits {
+ TxCSI = 0x80000000, TxHBI = 0x40000000, TxMLB = 0x20000000,
+ TxATP = 0x10000000, TxIFG = 0x0C000000, TxFILLT = 0x00003F00,
+ TxDRNT = 0x0000003F
+};
+
+/* recevie FIFO thresholds */
+#define RxDRNT_shift 1
+#define RxDRNT_100 16 /* 1/2 FIFO size */
+#define RxDRNT_10 24 /* 3/4 FIFO size */
+
+enum sis900_reveive_config_register_bits {
+ RxAEP = 0x80000000, RxARP = 0x40000000, RxATX = 0x10000000,
+ RxAJAB = 0x08000000, RxDRNT = 0x0000007F
+};
+
+#define RFAA_shift 28
+#define RFADDR_shift 16
+
+enum sis900_receive_filter_control_register_bits {
+ RFEN = 0x80000000, RFAAB = 0x40000000, RFAAM = 0x20000000,
+ RFAAP = 0x10000000, RFPromiscuous = (RFAAB|RFAAM|RFAAP)
+};
+
+enum sis900_reveive_filter_data_mask {
+ RFDAT = 0x0000FFFF
+};
+
+/* EEPROM Addresses */
+enum sis900_eeprom_address {
+ EEPROMSignature = 0x00, EEPROMVendorID = 0x02, EEPROMDeviceID = 0x03,
+ EEPROMMACAddr = 0x08, EEPROMChecksum = 0x0b
+};
+
+/* The EEPROM commands include the alway-set leading bit. Refer to NM93Cxx datasheet */
+enum sis900_eeprom_command {
+ EEread = 0x0180, EEwrite = 0x0140, EEerase = 0x01C0,
+ EEwriteEnable = 0x0130, EEwriteDisable = 0x0100,
+ EEeraseAll = 0x0120, EEwriteAll = 0x0110,
+ EEaddrMask = 0x013F, EEcmdShift = 16
+};
+
+/* For SiS962 or SiS963, request the eeprom software access */
+enum sis96x_eeprom_command {
+ EEREQ = 0x00000400, EEDONE = 0x00000200, EEGNT = 0x00000100
+};
+
+/* PCI Registers */
+enum sis900_pci_registers {
+ CFGPMC = 0x40,
+ CFGPMCSR = 0x44
+};
+
+/* Power management capabilities bits */
+enum sis900_cfgpmc_register_bits {
+ PMVER = 0x00070000,
+ DSI = 0x00100000,
+ PMESP = 0xf8000000
+};
+
+enum sis900_pmesp_bits {
+ PME_D0 = 0x1,
+ PME_D1 = 0x2,
+ PME_D2 = 0x4,
+ PME_D3H = 0x8,
+ PME_D3C = 0x10
+};
+
+/* Power management control/status bits */
+enum sis900_cfgpmcsr_register_bits {
+ PMESTS = 0x00004000,
+ PME_EN = 0x00000100, // Power management enable
+ PWR_STA = 0x00000003 // Current power state
+};
+
+/* Wake-on-LAN support. */
+enum sis900_power_management_control_register_bits {
+ LINKLOSS = 0x00000001,
+ LINKON = 0x00000002,
+ MAGICPKT = 0x00000400,
+ ALGORITHM = 0x00000800,
+ FRM1EN = 0x00100000,
+ FRM2EN = 0x00200000,
+ FRM3EN = 0x00400000,
+ FRM1ACS = 0x01000000,
+ FRM2ACS = 0x02000000,
+ FRM3ACS = 0x04000000,
+ WAKEALL = 0x40000000,
+ GATECLK = 0x80000000
+};
+
+/* Management Data I/O (mdio) frame */
+#define MIIread 0x6000
+#define MIIwrite 0x5002
+#define MIIpmdShift 7
+#define MIIregShift 2
+#define MIIcmdLen 16
+#define MIIcmdShift 16
+
+/* Buffer Descriptor Status*/
+enum sis900_buffer_status {
+ OWN = 0x80000000, MORE = 0x40000000, INTR = 0x20000000,
+ SUPCRC = 0x10000000, INCCRC = 0x10000000,
+ OK = 0x08000000, DSIZE = 0x00000FFF
+};
+/* Status for TX Buffers */
+enum sis900_tx_buffer_status {
+ ABORT = 0x04000000, UNDERRUN = 0x02000000, NOCARRIER = 0x01000000,
+ DEFERD = 0x00800000, EXCDEFER = 0x00400000, OWCOLL = 0x00200000,
+ EXCCOLL = 0x00100000, COLCNT = 0x000F0000
+};
+
+enum sis900_rx_bufer_status {
+ OVERRUN = 0x02000000, DEST = 0x00800000, BCAST = 0x01800000,
+ MCAST = 0x01000000, UNIMATCH = 0x00800000, TOOLONG = 0x00400000,
+ RUNT = 0x00200000, RXISERR = 0x00100000, CRCERR = 0x00080000,
+ FAERR = 0x00040000, LOOPBK = 0x00020000, RXCOL = 0x00010000
+};
+
+/* MII register offsets */
+enum mii_registers {
+ MII_CONTROL = 0x0000, MII_STATUS = 0x0001, MII_PHY_ID0 = 0x0002,
+ MII_PHY_ID1 = 0x0003, MII_ANADV = 0x0004, MII_ANLPAR = 0x0005,
+ MII_ANEXT = 0x0006
+};
+
+/* mii registers specific to SiS 900 */
+enum sis_mii_registers {
+ MII_CONFIG1 = 0x0010, MII_CONFIG2 = 0x0011, MII_STSOUT = 0x0012,
+ MII_MASK = 0x0013, MII_RESV = 0x0014
+};
+
+/* mii registers specific to ICS 1893 */
+enum ics_mii_registers {
+ MII_EXTCTRL = 0x0010, MII_QPDSTS = 0x0011, MII_10BTOP = 0x0012,
+ MII_EXTCTRL2 = 0x0013
+};
+
+/* mii registers specific to AMD 79C901 */
+enum amd_mii_registers {
+ MII_STATUS_SUMMARY = 0x0018
+};
+
+/* MII Control register bit definitions. */
+enum mii_control_register_bits {
+ MII_CNTL_FDX = 0x0100, MII_CNTL_RST_AUTO = 0x0200,
+ MII_CNTL_ISOLATE = 0x0400, MII_CNTL_PWRDWN = 0x0800,
+ MII_CNTL_AUTO = 0x1000, MII_CNTL_SPEED = 0x2000,
+ MII_CNTL_LPBK = 0x4000, MII_CNTL_RESET = 0x8000
+};
+
+/* MII Status register bit */
+enum mii_status_register_bits {
+ MII_STAT_EXT = 0x0001, MII_STAT_JAB = 0x0002,
+ MII_STAT_LINK = 0x0004, MII_STAT_CAN_AUTO = 0x0008,
+ MII_STAT_FAULT = 0x0010, MII_STAT_AUTO_DONE = 0x0020,
+ MII_STAT_CAN_T = 0x0800, MII_STAT_CAN_T_FDX = 0x1000,
+ MII_STAT_CAN_TX = 0x2000, MII_STAT_CAN_TX_FDX = 0x4000,
+ MII_STAT_CAN_T4 = 0x8000
+};
+
+#define MII_ID1_OUI_LO 0xFC00 /* low bits of OUI mask */
+#define MII_ID1_MODEL 0x03F0 /* model number */
+#define MII_ID1_REV 0x000F /* model number */
+
+/* MII NWAY Register Bits ...
+ valid for the ANAR (Auto-Negotiation Advertisement) and
+ ANLPAR (Auto-Negotiation Link Partner) registers */
+enum mii_nway_register_bits {
+ MII_NWAY_NODE_SEL = 0x001f, MII_NWAY_CSMA_CD = 0x0001,
+ MII_NWAY_T = 0x0020, MII_NWAY_T_FDX = 0x0040,
+ MII_NWAY_TX = 0x0080, MII_NWAY_TX_FDX = 0x0100,
+ MII_NWAY_T4 = 0x0200, MII_NWAY_PAUSE = 0x0400,
+ MII_NWAY_RF = 0x2000, MII_NWAY_ACK = 0x4000,
+ MII_NWAY_NP = 0x8000
+};
+
+enum mii_stsout_register_bits {
+ MII_STSOUT_LINK_FAIL = 0x4000,
+ MII_STSOUT_SPD = 0x0080, MII_STSOUT_DPLX = 0x0040
+};
+
+enum mii_stsics_register_bits {
+ MII_STSICS_SPD = 0x8000, MII_STSICS_DPLX = 0x4000,
+ MII_STSICS_LINKSTS = 0x0001
+};
+
+enum mii_stssum_register_bits {
+ MII_STSSUM_LINK = 0x0008, MII_STSSUM_DPLX = 0x0004,
+ MII_STSSUM_AUTO = 0x0002, MII_STSSUM_SPD = 0x0001
+};
+
+enum sis900_revision_id {
+ SIS630A_900_REV = 0x80, SIS630E_900_REV = 0x81,
+ SIS630S_900_REV = 0x82, SIS630EA1_900_REV = 0x83,
+ SIS630ET_900_REV = 0x84, SIS635A_900_REV = 0x90,
+ SIS96x_900_REV = 0X91, SIS900B_900_REV = 0x03
+};
+
+enum sis630_revision_id {
+ SIS630A0 = 0x00, SIS630A1 = 0x01,
+ SIS630B0 = 0x10, SIS630B1 = 0x11
+};
+
+#define FDX_CAPABLE_DUPLEX_UNKNOWN 0
+#define FDX_CAPABLE_HALF_SELECTED 1
+#define FDX_CAPABLE_FULL_SELECTED 2
+
+#define HW_SPEED_UNCONFIG 0
+#define HW_SPEED_HOME 1
+#define HW_SPEED_10_MBPS 10
+#define HW_SPEED_100_MBPS 100
+#define HW_SPEED_DEFAULT (HW_SPEED_100_MBPS)
+
+#define CRC_SIZE 4
+#define MAC_HEADER_SIZE 14
+
+#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
+#define MAX_FRAME_SIZE (1518 + 4)
+#else
+#define MAX_FRAME_SIZE 1518
+#endif /* CONFIG_VLAN_802_1Q */
+
+#define TX_BUF_SIZE (MAX_FRAME_SIZE+18)
+#define RX_BUF_SIZE (MAX_FRAME_SIZE+18)
+
+#define NUM_TX_DESC 16 /* Number of Tx descriptor registers. */
+#define NUM_RX_DESC 16 /* Number of Rx descriptor registers. */
+#define TX_TOTAL_SIZE NUM_TX_DESC*sizeof(BufferDesc)
+#define RX_TOTAL_SIZE NUM_RX_DESC*sizeof(BufferDesc)
+
+/* PCI stuff, should be move to pci.h */
+#define SIS630_VENDOR_ID 0x1039
+#define SIS630_DEVICE_ID 0x0630