]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/net/sis190.c
Merge master.kernel.org:/pub/scm/linux/kernel/git/davem/sparc-2.6
[linux-2.6-omap-h63xx.git] / drivers / net / sis190.c
index e67a5753882e6df92a6f598657a86f1dd116f250..bf3440aa6c248005dace01a7855a1aaabae1da65 100644 (file)
@@ -5,7 +5,8 @@
    Copyright (c) 2003, 2004 Jeff Garzik <jgarzik@pobox.com>
    Copyright (c) 2003, 2004, 2005 Francois Romieu <romieu@fr.zoreil.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 and probably even epic100.c.
+   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.
 
    This software may be used and distributed according to the terms of
    the GNU General Public License (GPL), incorporated herein by reference.
 #define net_tx_err(p, arg...)  if (netif_msg_tx_err(p)) \
                                        printk(arg)
 
 #define net_tx_err(p, arg...)  if (netif_msg_tx_err(p)) \
                                        printk(arg)
 
+#define PHY_MAX_ADDR           32
+#define PHY_ID_ANY             0x1f
+#define MII_REG_ANY            0x1f
+
 #ifdef CONFIG_SIS190_NAPI
 #define NAPI_SUFFIX    "-NAPI"
 #else
 #ifdef CONFIG_SIS190_NAPI
 #define NAPI_SUFFIX    "-NAPI"
 #else
 
 #define MAC_ADDR_LEN           6
 
 
 #define MAC_ADDR_LEN           6
 
-#define NUM_TX_DESC            64
-#define NUM_RX_DESC            64
+#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 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_REGS_SIZE       0x80
 #define SIS190_TX_TIMEOUT      (6*HZ)
@@ -147,12 +153,6 @@ enum sis190_register_content {
        RxHalt                  = 0x00000002,
        TxHalt                  = 0x00000001,
 
        RxHalt                  = 0x00000002,
        TxHalt                  = 0x00000001,
 
-       /* RxStatusDesc */
-       RxRES                   = 0x00200000,   // unused
-       RxCRC                   = 0x00080000,
-       RxRUNT                  = 0x00100000,   // unused
-       RxRWT                   = 0x00400000,   // unused
-
        /* {Rx/Tx}CmdBits */
        CmdReset                = 0x10,
        CmdRxEnb                = 0x08,         // unused
        /* {Rx/Tx}CmdBits */
        CmdReset                = 0x10,
        CmdRxEnb                = 0x08,         // unused
@@ -195,30 +195,88 @@ enum sis190_register_content {
 };
 
 struct TxDesc {
 };
 
 struct TxDesc {
-       u32 PSize;
-       u32 status;
-       u32 addr;
-       u32 size;
+       __le32 PSize;
+       __le32 status;
+       __le32 addr;
+       __le32 size;
 };
 
 struct RxDesc {
 };
 
 struct RxDesc {
-       u32 PSize;
-       u32 status;
-       u32 addr;
-       u32 size;
+       __le32 PSize;
+       __le32 status;
+       __le32 addr;
+       __le32 size;
 };
 
 enum _DescStatusBit {
        /* _Desc.status */
 };
 
 enum _DescStatusBit {
        /* _Desc.status */
-       OWNbit          = 0x80000000,
-       INTbit          = 0x40000000,
-       DEFbit          = 0x00200000,
-       CRCbit          = 0x00020000,
-       PADbit          = 0x00010000,
+       OWNbit          = 0x80000000, // RXOWN/TXOWN
+       INTbit          = 0x40000000, // RXINT/TXINT
+       CRCbit          = 0x00020000, // CRCOFF/CRCEN
+       PADbit          = 0x00010000, // PREADD/PADEN
        /* _Desc.size */
        /* _Desc.size */
-       RingEnd         = (1 << 31),
-       /* _Desc.PSize */
+       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,
+       /* 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
        RxSizeMask      = 0x0000ffff
+       /*
+        * The asic could apparently do vlan, TSO, jumbo (sis191 only) and
+        * provide two (unused with Linux) Tx queues. No publically
+        * 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
 };
 
 struct sis190_private {
 };
 
 struct sis190_private {
@@ -241,6 +299,34 @@ struct sis190_private {
        struct timer_list timer;
        u32 msg_enable;
        struct mii_if_info mii_if;
        struct timer_list timer;
        u32 msg_enable;
        struct mii_if_info mii_if;
+       struct list_head first_phy;
+};
+
+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;
+} mii_chip_table[] = {
+       { "Broadcom PHY BCM5461", { 0x0020, 0x60c0 }, LAN },
+       { "Agere PHY ET1101B",    { 0x0282, 0xf010 }, LAN },
+       { "Marvell PHY 88E1111",  { 0x0141, 0x0cc0 }, LAN },
+       { "Realtek PHY RTL8201",  { 0x0000, 0x8200 }, LAN },
+       { NULL, }
 };
 
 const static struct {
 };
 
 const static struct {
@@ -300,21 +386,17 @@ static void __mdio_cmd(void __iomem *ioaddr, u32 ctl)
                printk(KERN_ERR PFX "PHY command failed !\n");
 }
 
                printk(KERN_ERR PFX "PHY command failed !\n");
 }
 
-static void mdio_write(void __iomem *ioaddr, int reg, int val)
+static void mdio_write(void __iomem *ioaddr, int phy_id, int reg, int val)
 {
 {
-       u32 pmd = 1;
-
        __mdio_cmd(ioaddr, EhnMIIreq | EhnMIIwrite |
        __mdio_cmd(ioaddr, EhnMIIreq | EhnMIIwrite |
-               (((u32) reg) << EhnMIIregShift) | (pmd << EhnMIIpmdShift) |
+               (((u32) reg) << EhnMIIregShift) | (phy_id << EhnMIIpmdShift) |
                (((u32) val) << EhnMIIdataShift));
 }
 
                (((u32) val) << EhnMIIdataShift));
 }
 
-static int mdio_read(void __iomem *ioaddr, int reg)
+static int mdio_read(void __iomem *ioaddr, int phy_id, int reg)
 {
 {
-       u32 pmd = 1;
-
        __mdio_cmd(ioaddr, EhnMIIreq | EhnMIIread |
        __mdio_cmd(ioaddr, EhnMIIreq | EhnMIIread |
-               (((u32) reg) << EhnMIIregShift) | (pmd << EhnMIIpmdShift));
+               (((u32) reg) << EhnMIIregShift) | (phy_id << EhnMIIpmdShift));
 
        return (u16) (SIS_R32(GMIIControl) >> EhnMIIdataShift);
 }
 
        return (u16) (SIS_R32(GMIIControl) >> EhnMIIdataShift);
 }
@@ -323,37 +405,40 @@ static void __mdio_write(struct net_device *dev, int phy_id, int reg, int val)
 {
        struct sis190_private *tp = netdev_priv(dev);
 
 {
        struct sis190_private *tp = netdev_priv(dev);
 
-       mdio_write(tp->mmio_addr, reg, val);
+       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);
 
 }
 
 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, reg);
+       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 int sis190_read_eeprom(void __iomem *ioaddr, u32 reg)
+static u16 __devinit sis190_read_eeprom(void __iomem *ioaddr, u32 reg)
 {
 {
+       u16 data = 0xffff;
        unsigned int i;
        unsigned int i;
-       u16 data;
-       u32 val;
 
        if (!(SIS_R32(ROMControl) & 0x0002))
                return 0;
 
 
        if (!(SIS_R32(ROMControl) & 0x0002))
                return 0;
 
-       val = (0x0080 | (0x2 << 8) | (reg << 10));
-
-       SIS_W32(ROMInterface, val);
+       SIS_W32(ROMInterface, EEREQ | EEROP | (reg << 10));
 
        for (i = 0; i < 200; i++) {
 
        for (i = 0; i < 200; i++) {
-               if (!(SIS_R32(ROMInterface) & 0x0080))
+               if (!(SIS_R32(ROMInterface) & EEREQ)) {
+                       data = (SIS_R32(ROMInterface) & 0xffff0000) >> 16;
                        break;
                        break;
+               }
                msleep(1);
        }
 
                msleep(1);
        }
 
-       data = (u16) ((SIS_R32(ROMInterface) & 0xffff0000) >> 16);
-
        return data;
 }
 
        return data;
 }
 
@@ -384,7 +469,7 @@ 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;
        u32 eor = le32_to_cpu(desc->size) & RingEnd;
 
        desc->PSize = 0x0;
-       desc->size = cpu_to_le32(rx_buf_sz | eor);
+       desc->size = cpu_to_le32((rx_buf_sz & RX_BUF_MASK) | eor);
        wmb();
        desc->status = cpu_to_le32(OWNbit | INTbit);
 }
        wmb();
        desc->status = cpu_to_le32(OWNbit | INTbit);
 }
@@ -470,6 +555,26 @@ static inline int sis190_try_rx_copy(struct sk_buff **sk_buff, int pkt_size,
        return ret;
 }
 
        return ret;
 }
 
+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)
 {
 static int sis190_rx_interrupt(struct net_device *dev,
                               struct sis190_private *tp, void __iomem *ioaddr)
 {
@@ -493,19 +598,9 @@ static int sis190_rx_interrupt(struct net_device *dev,
                // net_intr(tp, KERN_INFO "%s: Rx PSize = %08x.\n", dev->name,
                //       status);
 
                // net_intr(tp, KERN_INFO "%s: Rx PSize = %08x.\n", dev->name,
                //       status);
 
-               if (status & RxCRC) {
-                       net_intr(tp, KERN_INFO "%s: bad crc. status = %08x.\n",
-                                dev->name, status);
-                       stats->rx_errors++;
-                       stats->rx_crc_errors++;
+               if (sis190_rx_pkt_err(status, stats) < 0)
                        sis190_give_to_asic(desc, tp->rx_buf_sz);
                        sis190_give_to_asic(desc, tp->rx_buf_sz);
-               } else if (!(status & PADbit)) {
-                       net_intr(tp, KERN_INFO "%s: bad pad. status = %08x.\n",
-                                dev->name, status);
-                       stats->rx_errors++;
-                       stats->rx_length_errors++;
-                       sis190_give_to_asic(desc, tp->rx_buf_sz);
-               } else {
+               else {
                        struct sk_buff *skb = tp->Rx_skbuff[entry];
                        int pkt_size = (status & RxSizeMask) - 4;
                        void (*pci_action)(struct pci_dev *, dma_addr_t,
                        struct sk_buff *skb = tp->Rx_skbuff[entry];
                        int pkt_size = (status & RxSizeMask) - 4;
                        void (*pci_action)(struct pci_dev *, dma_addr_t,
@@ -542,8 +637,10 @@ static int sis190_rx_interrupt(struct net_device *dev,
                        sis190_rx_skb(skb);
 
                        dev->last_rx = jiffies;
                        sis190_rx_skb(skb);
 
                        dev->last_rx = jiffies;
-                       stats->rx_bytes += pkt_size;
                        stats->rx_packets++;
                        stats->rx_packets++;
+                       stats->rx_bytes += pkt_size;
+                       if ((status & BCAST) == MCAST)
+                               stats->multicast++;
                }
        }
        count = cur_rx - tp->cur_rx;
                }
        }
        count = cur_rx - tp->cur_rx;
@@ -813,18 +910,20 @@ static void sis190_phy_task(void * data)
        struct net_device *dev = data;
        struct sis190_private *tp = netdev_priv(dev);
        void __iomem *ioaddr = tp->mmio_addr;
        struct net_device *dev = data;
        struct sis190_private *tp = netdev_priv(dev);
        void __iomem *ioaddr = tp->mmio_addr;
+       int phy_id = tp->mii_if.phy_id;
        u16 val;
 
        rtnl_lock();
 
        u16 val;
 
        rtnl_lock();
 
-       val = mdio_read(ioaddr, MII_BMCR);
+       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);
        if (val & BMCR_RESET) {
                // FIXME: needlessly high ?  -- FR 02/07/2005
                mod_timer(&tp->timer, jiffies + HZ/10);
-       } else if (!(mdio_read(ioaddr, MII_BMSR) & BMSR_ANEGCOMPLETE)) {
+       } else if (!(mdio_read_latched(ioaddr, phy_id, MII_BMSR) &
+                    BMSR_ANEGCOMPLETE)) {
                net_link(tp, KERN_WARNING "%s: PHY reset until link up.\n",
                         dev->name);
                net_link(tp, KERN_WARNING "%s: PHY reset until link up.\n",
                         dev->name);
-               mdio_write(ioaddr, MII_BMCR, val | BMCR_RESET);
+               mdio_write(ioaddr, phy_id, MII_BMCR, val | BMCR_RESET);
                mod_timer(&tp->timer, jiffies + SIS190_PHY_TIMEOUT);
        } else {
                /* Rejoice ! */
                mod_timer(&tp->timer, jiffies + SIS190_PHY_TIMEOUT);
        } else {
                /* Rejoice ! */
@@ -853,12 +952,17 @@ static void sis190_phy_task(void * data)
                                0x01 | _10bpsH },
                        { 0, "unknown", 0x0000 }
                }, *p;
                                0x01 | _10bpsH },
                        { 0, "unknown", 0x0000 }
                }, *p;
+               u16 adv;
 
 
-               val = mdio_read(ioaddr, 0x1f);
+               val = mdio_read(ioaddr, phy_id, 0x1f);
                net_link(tp, KERN_INFO "%s: mii ext = %04x.\n", dev->name, val);
 
                net_link(tp, KERN_INFO "%s: mii ext = %04x.\n", dev->name, val);
 
-               val = mdio_read(ioaddr, MII_LPA);
-               net_link(tp, KERN_INFO "%s: mii lpa = %04x.\n", dev->name, val);
+               val = mdio_read(ioaddr, phy_id, MII_LPA);
+               adv = mdio_read(ioaddr, phy_id, MII_ADVERTISE);
+               net_link(tp, KERN_INFO "%s: mii lpa = %04x adv = %04x.\n",
+                        dev->name, val, adv);
+
+               val &= adv;
 
                for (p = reg31; p->ctl; p++) {
                        if ((val & p->val) == p->val)
 
                for (p = reg31; p->ctl; p++) {
                        if ((val & p->val) == p->val)
@@ -908,6 +1012,11 @@ static void sis190_set_rxbufsize(struct sis190_private *tp,
        unsigned int mtu = dev->mtu;
 
        tp->rx_buf_sz = (mtu > RX_BUF_SIZE) ? mtu + ETH_HLEN + 8 : RX_BUF_SIZE;
        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)
 }
 
 static int sis190_open(struct net_device *dev)
@@ -1102,6 +1211,177 @@ static struct net_device_stats *sis190_get_stats(struct net_device *dev)
        return &tp->stats;
 }
 
        return &tp->stats;
 }
 
+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_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;
+               net_probe(tp, KERN_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;
+       } else
+               phy->type = UNKNOWN;
+
+       net_probe(tp, KERN_INFO "%s: %s transceiver at address %d.\n",
+                 pci_name(tp->pci_dev),
+                 (phy->type == UNKNOWN) ? "Unknown PHY" : p->name, phy_id);
+}
+
+/**
+ *     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)) {
+               net_probe(tp, KERN_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);
+
+       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 __devexit 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);
 static void sis190_release_board(struct pci_dev *pdev)
 {
        struct net_device *dev = pci_get_drvdata(pdev);
@@ -1179,13 +1459,6 @@ static struct net_device * __devinit sis190_init_board(struct pci_dev *pdev)
        tp->pci_dev = pdev;
        tp->mmio_addr = ioaddr;
 
        tp->pci_dev = pdev;
        tp->mmio_addr = ioaddr;
 
-       tp->mii_if.dev = dev;
-       tp->mii_if.mdio_read = __mdio_read;
-       tp->mii_if.mdio_write = __mdio_write;
-       // tp->mii_if.phy_id = XXX;
-       tp->mii_if.phy_id_mask = 0x1f;
-       tp->mii_if.reg_num_mask = 0x1f;
-
        sis190_irq_mask_and_ack(ioaddr);
 
        sis190_soft_reset(ioaddr);
        sis190_irq_mask_and_ack(ioaddr);
 
        sis190_soft_reset(ioaddr);
@@ -1232,27 +1505,147 @@ static void sis190_tx_timeout(struct net_device *dev)
        netif_wake_queue(dev);
 }
 
        netif_wake_queue(dev);
 }
 
+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;
+
+       net_probe(tp, KERN_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)) {
+               net_probe(tp, KERN_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++) {
+               __le16 w = sis190_read_eeprom(ioaddr, EEPROMMACAddr + i);
+
+               ((u16 *)dev->dev_addr)[0] = le16_to_cpu(w);
+       }
+
+       return 0;
+}
+
+/**
+ *     sis190_get_mac_addr_from_apc - Get MAC address for SiS965 model
+ *     @pdev: PCI device
+ *     @dev:  network device to get address for
+ *
+ *     SiS965 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)
+{
+       struct sis190_private *tp = netdev_priv(dev);
+       struct pci_dev *isa_bridge;
+       u8 reg, tmp8;
+       int i;
+
+       net_probe(tp, KERN_INFO "%s: Read MAC address from APC.\n",
+                 pci_name(pdev));
+
+       isa_bridge = pci_get_device(PCI_VENDOR_ID_SI, 0x0965, NULL);
+       if (!isa_bridge) {
+               net_probe(tp, KERN_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);
+
+       /* 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 sis190_get_mac_addr(struct pci_dev *pdev, struct net_device *dev)
+{
+       u8 from;
+
+       pci_read_config_byte(pdev, 0x73, &from);
+
+       return (from & 0x00000001) ?
+               sis190_get_mac_addr_from_apc(pdev, dev) :
+               sis190_get_mac_addr_from_eeprom(pdev, dev);
+}
+
 static void sis190_set_speed_auto(struct net_device *dev)
 {
        struct sis190_private *tp = netdev_priv(dev);
        void __iomem *ioaddr = tp->mmio_addr;
 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;
 
        net_link(tp, KERN_INFO "%s: Enabling Auto-negotiation.\n", dev->name);
 
        int val;
 
        net_link(tp, KERN_INFO "%s: Enabling Auto-negotiation.\n", dev->name);
 
-       val = mdio_read(ioaddr, MII_ADVERTISE);
+       val = mdio_read(ioaddr, phy_id, MII_ADVERTISE);
 
        // Enable 10/100 Full/Half Mode, leave MII_ADVERTISE bit4:0
        // unchanged.
 
        // Enable 10/100 Full/Half Mode, leave MII_ADVERTISE bit4:0
        // unchanged.
-       mdio_write(ioaddr, MII_ADVERTISE, (val & ADVERTISE_SLCT) |
+       mdio_write(ioaddr, phy_id, MII_ADVERTISE, (val & ADVERTISE_SLCT) |
                   ADVERTISE_100FULL | ADVERTISE_10FULL |
                   ADVERTISE_100HALF | ADVERTISE_10HALF);
 
        // Enable 1000 Full Mode.
                   ADVERTISE_100FULL | ADVERTISE_10FULL |
                   ADVERTISE_100HALF | ADVERTISE_10HALF);
 
        // Enable 1000 Full Mode.
-       mdio_write(ioaddr, MII_CTRL1000, ADVERTISE_1000FULL);
+       mdio_write(ioaddr, phy_id, MII_CTRL1000, ADVERTISE_1000FULL);
 
        // Enable auto-negotiation and restart auto-negotiation.
 
        // Enable auto-negotiation and restart auto-negotiation.
-       mdio_write(ioaddr, MII_BMCR,
+       mdio_write(ioaddr, phy_id, MII_BMCR,
                   BMCR_ANENABLE | BMCR_ANRESTART | BMCR_RESET);
 }
 
                   BMCR_ANENABLE | BMCR_ANRESTART | BMCR_RESET);
 }
 
@@ -1347,7 +1740,7 @@ static int __devinit sis190_init_one(struct pci_dev *pdev,
        struct sis190_private *tp;
        struct net_device *dev;
        void __iomem *ioaddr;
        struct sis190_private *tp;
        struct net_device *dev;
        void __iomem *ioaddr;
-       int i, rc;
+       int rc;
 
        if (!printed_version) {
                net_drv(&debug, KERN_INFO SIS190_DRIVER_NAME " loaded.\n");
 
        if (!printed_version) {
                net_drv(&debug, KERN_INFO SIS190_DRIVER_NAME " loaded.\n");
@@ -1363,18 +1756,11 @@ static int __devinit sis190_init_one(struct pci_dev *pdev,
        tp = netdev_priv(dev);
        ioaddr = tp->mmio_addr;
 
        tp = netdev_priv(dev);
        ioaddr = tp->mmio_addr;
 
-       /* Get MAC address */
-       /* Read node address from the EEPROM */
-
-       if (SIS_R32(ROMControl) & 0x4) {
-               for (i = 0; i < 3; i++) {
-                       SIS_W16(RxMacAddr + 2*i,
-                               sis190_read_eeprom(ioaddr, 3 + i));
-               }
-       }
+       rc = sis190_get_mac_addr(pdev, dev);
+       if (rc < 0)
+               goto err_release_board;
 
 
-       for (i = 0; i < MAC_ADDR_LEN; i++)
-               dev->dev_addr[i] = SIS_R8(RxMacAddr + i);
+       sis190_init_rxfilter(dev);
 
        INIT_WORK(&tp->phy_task, sis190_phy_task, dev);
 
 
        INIT_WORK(&tp->phy_task, sis190_phy_task, dev);
 
@@ -1394,11 +1780,14 @@ static int __devinit sis190_init_one(struct pci_dev *pdev,
        dev->base_addr = (unsigned long) 0xdead;
 
        spin_lock_init(&tp->lock);
        dev->base_addr = (unsigned long) 0xdead;
 
        spin_lock_init(&tp->lock);
+
+       rc = sis190_mii_probe(dev);
+       if (rc < 0)
+               goto err_release_board;
+
        rc = register_netdev(dev);
        rc = register_netdev(dev);
-       if (rc < 0) {
-               sis190_release_board(pdev);
-               goto out;
-       }
+       if (rc < 0)
+               goto err_remove_mii;
 
        pci_set_drvdata(pdev, dev);
 
 
        pci_set_drvdata(pdev, dev);
 
@@ -1415,12 +1804,19 @@ static int __devinit sis190_init_one(struct pci_dev *pdev,
        sis190_set_speed_auto(dev);
 out:
        return rc;
        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);
 
 }
 
 static void __devexit sis190_remove_one(struct pci_dev *pdev)
 {
        struct net_device *dev = pci_get_drvdata(pdev);
 
+       sis190_mii_remove(dev);
        unregister_netdev(dev);
        sis190_release_board(pdev);
        pci_set_drvdata(pdev, NULL);
        unregister_netdev(dev);
        sis190_release_board(pdev);
        pci_set_drvdata(pdev, NULL);