[PATCH] sis190: unmask the link change events
[firefly-linux-kernel-4.4.55.git] / drivers / net / sis190.c
index 3c33b2d14852bb8c291cf9a3e5b19ff36a2213be..5f1d0ad549693af9e253ad3f8d44216782992361 100644 (file)
 #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
@@ -191,17 +195,17 @@ enum sis190_register_content {
 };
 
 struct TxDesc {
-       u32 PSize;
-       u32 status;
-       u32 addr;
-       u32 size;
+       __le32 PSize;
+       __le32 status;
+       __le32 addr;
+       __le32 size;
 };
 
 struct RxDesc {
-       u32 PSize;
-       u32 status;
-       u32 addr;
-       u32 size;
+       __le32 PSize;
+       __le32 status;
+       __le32 addr;
+       __le32 size;
 };
 
 enum _DescStatusBit {
@@ -295,6 +299,34 @@ struct sis190_private {
        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 {
@@ -328,7 +360,7 @@ MODULE_VERSION(DRV_VERSION);
 MODULE_LICENSE("GPL");
 
 static const u32 sis190_intr_mask =
-       RxQEmpty | RxQInt | TxQ1Int | TxQ0Int | RxHalt | TxHalt;
+       RxQEmpty | RxQInt | TxQ1Int | TxQ0Int | RxHalt | TxHalt | LinkChange;
 
 /*
  * Maximum number of multicast addresses to filter (vs. Rx-all-multicast).
@@ -354,21 +386,17 @@ static void __mdio_cmd(void __iomem *ioaddr, u32 ctl)
                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 |
-               (((u32) reg) << EhnMIIregShift) | (pmd << EhnMIIpmdShift) |
+               (((u32) reg) << EhnMIIregShift) | (phy_id << EhnMIIpmdShift) |
                (((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 |
-               (((u32) reg) << EhnMIIregShift) | (pmd << EhnMIIpmdShift));
+               (((u32) reg) << EhnMIIregShift) | (phy_id << EhnMIIpmdShift));
 
        return (u16) (SIS_R32(GMIIControl) >> EhnMIIdataShift);
 }
@@ -377,14 +405,20 @@ 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, 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);
 
-       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 u16 __devinit sis190_read_eeprom(void __iomem *ioaddr, u32 reg)
@@ -876,18 +910,21 @@ 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;
+       int phy_id = tp->mii_if.phy_id;
        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);
-       } 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);
-               mdio_write(ioaddr, MII_BMCR, val | BMCR_RESET);
+               netif_carrier_off(dev);
+               mdio_write(ioaddr, phy_id, MII_BMCR, val | BMCR_RESET);
                mod_timer(&tp->timer, jiffies + SIS190_PHY_TIMEOUT);
        } else {
                /* Rejoice ! */
@@ -916,12 +953,17 @@ static void sis190_phy_task(void * data)
                                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);
 
-               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)
@@ -1170,6 +1212,177 @@ static struct net_device_stats *sis190_get_stats(struct net_device *dev)
        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);
@@ -1247,13 +1460,6 @@ static struct net_device * __devinit sis190_init_board(struct pci_dev *pdev)
        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);
@@ -1322,7 +1528,7 @@ static int __devinit sis190_get_mac_addr_from_eeprom(struct pci_dev *pdev,
 
        /* Get MAC address from EEPROM */
        for (i = 0; i < MAC_ADDR_LEN / 2; i++) {
-               u16 w = sis190_read_eeprom(ioaddr, EEPROMMACAddr + i);
+               __le16 w = sis190_read_eeprom(ioaddr, EEPROMMACAddr + i);
 
                ((u16 *)dev->dev_addr)[0] = le16_to_cpu(w);
        }
@@ -1423,23 +1629,24 @@ 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);
 
-       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.
-       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.
-       mdio_write(ioaddr, MII_CTRL1000, ADVERTISE_1000FULL);
+       mdio_write(ioaddr, phy_id, MII_CTRL1000, ADVERTISE_1000FULL);
 
        // 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);
 }
 
@@ -1574,10 +1781,15 @@ static int __devinit sis190_init_one(struct pci_dev *pdev,
        dev->base_addr = (unsigned long) 0xdead;
 
        spin_lock_init(&tp->lock);
-       rc = register_netdev(dev);
+
+       rc = sis190_mii_probe(dev);
        if (rc < 0)
                goto err_release_board;
 
+       rc = register_netdev(dev);
+       if (rc < 0)
+               goto err_remove_mii;
+
        pci_set_drvdata(pdev, dev);
 
        net_probe(tp, KERN_INFO "%s: %s at %p (IRQ: %d), "
@@ -1594,6 +1806,8 @@ static int __devinit sis190_init_one(struct pci_dev *pdev,
 out:
        return rc;
 
+err_remove_mii:
+       sis190_mii_remove(dev);
 err_release_board:
        sis190_release_board(pdev);
        goto out;
@@ -1603,6 +1817,7 @@ 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);