ar8216: add adjust_link checking
[openwrt/openwrt.git] / target / linux / generic / files / drivers / net / phy / ar8216.c
index 817aca57d593e7435498bebd78cedf3b6e2cbef4..6869e1ff191cbba219a7719fedd94cbc1b8595e4 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * ar8216.c: AR8216 switch driver
  *
- * Copyright (C) 2009 Felix Fietkau <nbd@openwrt.org>
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
  * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
  *
  * This program is free software; you can redistribute it and/or
@@ -177,7 +177,7 @@ ar8xxx_phy_check_aneg(struct phy_device *phydev)
        if (ret & BMCR_ANENABLE)
                return 0;
 
-       dev_info(&phydev->dev, "ANEG disabled, re-enabling ...\n");
+       dev_info(&phydev->mdio.dev, "ANEG disabled, re-enabling ...\n");
        ret |= BMCR_ANENABLE | BMCR_ANRESTART;
        return phy_write(phydev, MII_BMCR, ret);
 }
@@ -308,25 +308,33 @@ ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr,
        mutex_unlock(&bus->mdio_lock);
 }
 
+static inline void
+ar8xxx_phy_mmd_prep(struct mii_bus *bus, int phy_addr, u16 addr, u16 reg)
+{
+       bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr);
+       bus->write(bus, phy_addr, MII_ATH_MMD_DATA, reg);
+       bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr | 0x4000);
+}
+
 void
-ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 data)
+ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data)
 {
        struct mii_bus *bus = priv->mii_bus;
 
        mutex_lock(&bus->mdio_lock);
-       bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr);
+       ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg);
        bus->write(bus, phy_addr, MII_ATH_MMD_DATA, data);
        mutex_unlock(&bus->mdio_lock);
 }
 
 u16
-ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr)
+ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg)
 {
        struct mii_bus *bus = priv->mii_bus;
        u16 data;
 
        mutex_lock(&bus->mdio_lock);
-       bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr);
+       ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg);
        data = bus->read(bus, phy_addr, MII_ATH_MMD_DATA);
        mutex_unlock(&bus->mdio_lock);
 
@@ -347,6 +355,7 @@ ar8xxx_reg_wait(struct ar8xxx_priv *priv, u32 reg, u32 mask, u32 val,
                        return 0;
 
                usleep_range(1000, 2000);
+               cond_resched();
        }
 
        return -ETIMEDOUT;
@@ -418,6 +427,7 @@ ar8xxx_mib_fetch_port_stat(struct ar8xxx_priv *priv, int port, bool flush)
                        mib_stats[i] = 0;
                else
                        mib_stats[i] += t;
+               cond_resched();
        }
 }
 
@@ -528,7 +538,7 @@ ar8216_mangle_rx(struct net_device *dev, struct sk_buff *skb)
        if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00))
                return;
 
-       port = buf[0] & 0xf;
+       port = buf[0] & 0x7;
 
        /* no need to fix up packets coming from a tagged source */
        if (priv->vlan_tagged & (1 << port))
@@ -557,6 +567,7 @@ ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val)
                        break;
 
                udelay(10);
+               cond_resched();
        }
 
        pr_err("ar8216: timeout on reg %08x: %08x & %08x != %08x\n",
@@ -722,8 +733,10 @@ ar8216_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1)
 {
        int timeout = 20;
 
-       while (ar8xxx_mii_read32(priv, r2, r1) & AR8216_ATU_ACTIVE && --timeout)
-                udelay(10);
+       while (ar8xxx_mii_read32(priv, r2, r1) & AR8216_ATU_ACTIVE && --timeout) {
+               udelay(10);
+               cond_resched();
+       }
 
        if (!timeout)
                pr_err("ar8216: timeout waiting for atu to become ready\n");
@@ -941,7 +954,8 @@ ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan)
 
        /* make sure no invalid PVIDs get set */
 
-       if (vlan >= dev->vlans)
+       if (vlan < 0 || vlan >= dev->vlans ||
+           port < 0 || port >= AR8X16_MAX_PORTS)
                return -EINVAL;
 
        priv->pvid[port] = vlan;
@@ -952,6 +966,10 @@ int
 ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan)
 {
        struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+       if (port < 0 || port >= AR8X16_MAX_PORTS)
+               return -EINVAL;
+
        *vlan = priv->pvid[port];
        return 0;
 }
@@ -961,6 +979,10 @@ ar8xxx_sw_set_vid(struct switch_dev *dev, const struct switch_attr *attr,
                  struct switch_val *val)
 {
        struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+       if (val->port_vlan >= AR8X16_MAX_VLANS)
+               return -EINVAL;
+
        priv->vlan_id[val->port_vlan] = val->value.i;
        return 0;
 }
@@ -988,9 +1010,13 @@ static int
 ar8xxx_sw_get_ports(struct switch_dev *dev, struct switch_val *val)
 {
        struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
-       u8 ports = priv->vlan_table[val->port_vlan];
+       u8 ports;
        int i;
 
+       if (val->port_vlan >= AR8X16_MAX_VLANS)
+               return -EINVAL;
+
+       ports = priv->vlan_table[val->port_vlan];
        val->len = 0;
        for (i = 0; i < dev->ports; i++) {
                struct switch_port *p;
@@ -1173,6 +1199,7 @@ ar8xxx_sw_reset_switch(struct switch_dev *dev)
        priv->arl_age_time = AR8XXX_DEFAULT_ARL_AGE_TIME;
 
        chip->init_globals(priv);
+       chip->atu_flush(priv);
 
        mutex_unlock(&priv->reg_mutex);
 
@@ -1370,7 +1397,7 @@ ar8xxx_sw_get_port_mib(struct switch_dev *dev,
        struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
        const struct ar8xxx_chip *chip = priv->chip;
        u64 *mib_stats, mib_data;
-       int port;
+       unsigned int port;
        int ret;
        char *buf = priv->buf;
        char buf1[64];
@@ -1674,6 +1701,16 @@ static const struct switch_dev_ops ar8xxx_sw_ops = {
        .apply_config = ar8xxx_sw_hw_apply,
        .reset_switch = ar8xxx_sw_reset_switch,
        .get_port_link = ar8xxx_sw_get_port_link,
+/* The following op is disabled as it hogs the CPU and degrades performance.
+   An implementation has been attempted in 4d8a66d but reading MIB data is slow
+   on ar8xxx switches.
+
+   The high CPU load has been traced down to the ar8xxx_reg_wait() call in
+   ar8xxx_mib_op(), which has to usleep_range() till the MIB busy flag set by
+   the request to update the MIB counter is cleared. */
+#if 0
+       .get_port_stats = ar8xxx_sw_get_port_stats,
+#endif
 };
 
 static const struct ar8xxx_chip ar8216_chip = {
@@ -1881,7 +1918,7 @@ ar8xxx_mib_stop(struct ar8xxx_priv *priv)
        if (!ar8xxx_has_mib_counters(priv))
                return;
 
-       cancel_delayed_work(&priv->mib_work);
+       cancel_delayed_work_sync(&priv->mib_work);
 }
 
 static struct ar8xxx_priv *
@@ -1975,7 +2012,7 @@ ar8xxx_phy_config_init(struct phy_device *phydev)
 
        priv->phy = phydev;
 
-       if (phydev->addr != 0) {
+       if (phydev->mdio.addr != 0) {
                if (chip_is_ar8316(priv)) {
                        /* switch device has been initialized, reinit */
                        priv->dev.ports = (AR8216_NUM_PORTS - 1);
@@ -2023,7 +2060,7 @@ ar8xxx_check_link_states(struct ar8xxx_priv *priv)
                /* flush ARL entries for this port if it went down*/
                if (!link_new)
                        priv->chip->atu_flush_port(priv, i);
-               dev_info(&priv->phy->dev, "Port %d is %s\n",
+               dev_info(&priv->phy->mdio.dev, "Port %d is %s\n",
                         i, link_new ? "up" : "down");
        }
 
@@ -2042,10 +2079,10 @@ ar8xxx_phy_read_status(struct phy_device *phydev)
        if (phydev->state == PHY_CHANGELINK)
                ar8xxx_check_link_states(priv);
 
-       if (phydev->addr != 0)
+       if (phydev->mdio.addr != 0)
                return genphy_read_status(phydev);
 
-       ar8216_read_port_link(priv, phydev->addr, &link);
+       ar8216_read_port_link(priv, phydev->mdio.addr, &link);
        phydev->link = !!link.link;
        if (!phydev->link)
                return 0;
@@ -2067,7 +2104,8 @@ ar8xxx_phy_read_status(struct phy_device *phydev)
 
        phydev->state = PHY_RUNNING;
        netif_carrier_on(phydev->attached_dev);
-       phydev->adjust_link(phydev->attached_dev);
+       if (phydev->adjust_link)
+               phydev->adjust_link(phydev->attached_dev);
 
        return 0;
 }
@@ -2075,7 +2113,7 @@ ar8xxx_phy_read_status(struct phy_device *phydev)
 static int
 ar8xxx_phy_config_aneg(struct phy_device *phydev)
 {
-       if (phydev->addr == 0)
+       if (phydev->mdio.addr == 0)
                return 0;
 
        return genphy_config_aneg(phydev);
@@ -2105,21 +2143,21 @@ ar8xxx_phy_match(u32 phy_id)
 static bool
 ar8xxx_is_possible(struct mii_bus *bus)
 {
-       unsigned i;
+       unsigned int i, found_phys = 0;
 
-       for (i = 0; i < 4; i++) {
+       for (i = 0; i < 5; i++) {
                u32 phy_id;
 
                phy_id = mdiobus_read(bus, i, MII_PHYSID1) << 16;
                phy_id |= mdiobus_read(bus, i, MII_PHYSID2);
-               if (!ar8xxx_phy_match(phy_id)) {
+               if (ar8xxx_phy_match(phy_id)) {
+                       found_phys++;
+               } else if (phy_id) {
                        pr_debug("ar8xxx: unknown PHY at %s:%02x id:%08x\n",
                                 dev_name(&bus->dev), i, phy_id);
-                       return false;
                }
        }
-
-       return true;
+       return !!found_phys;
 }
 
 static int
@@ -2130,15 +2168,15 @@ ar8xxx_phy_probe(struct phy_device *phydev)
        int ret;
 
        /* skip PHYs at unused adresses */
-       if (phydev->addr != 0 && phydev->addr != 4)
+       if (phydev->mdio.addr != 0 && phydev->mdio.addr != 4)
                return -ENODEV;
 
-       if (!ar8xxx_is_possible(phydev->bus))
+       if (!ar8xxx_is_possible(phydev->mdio.bus))
                return -ENODEV;
 
        mutex_lock(&ar8xxx_dev_list_lock);
        list_for_each_entry(priv, &ar8xxx_dev_list, list)
-               if (priv->mii_bus == phydev->bus)
+               if (priv->mii_bus == phydev->mdio.bus)
                        goto found;
 
        priv = ar8xxx_create();
@@ -2147,7 +2185,7 @@ ar8xxx_phy_probe(struct phy_device *phydev)
                goto unlock;
        }
 
-       priv->mii_bus = phydev->bus;
+       priv->mii_bus = phydev->mdio.bus;
 
        ret = ar8xxx_probe_switch(priv);
        if (ret)
@@ -2163,10 +2201,12 @@ ar8xxx_phy_probe(struct phy_device *phydev)
                swdev->devname, swdev->name, priv->chip_rev,
                dev_name(&priv->mii_bus->dev));
 
+       list_add(&priv->list, &ar8xxx_dev_list);
+
 found:
        priv->use_count++;
 
-       if (phydev->addr == 0) {
+       if (phydev->mdio.addr == 0) {
                if (ar8xxx_has_gige(priv)) {
                        phydev->supported = SUPPORTED_1000baseT_Full;
                        phydev->advertising = ADVERTISED_1000baseT_Full;
@@ -2191,8 +2231,6 @@ found:
 
        phydev->priv = priv;
 
-       list_add(&priv->list, &ar8xxx_dev_list);
-
        mutex_unlock(&ar8xxx_dev_list_lock);
 
        return 0;
@@ -2233,10 +2271,14 @@ ar8xxx_phy_remove(struct phy_device *phydev)
                return;
 
        phydev->priv = NULL;
-       if (--priv->use_count > 0)
-               return;
 
        mutex_lock(&ar8xxx_dev_list_lock);
+
+       if (--priv->use_count > 0) {
+               mutex_unlock(&ar8xxx_dev_list_lock);
+               return;
+       }
+
        list_del(&priv->list);
        mutex_unlock(&ar8xxx_dev_list_lock);
 
@@ -2245,45 +2287,28 @@ ar8xxx_phy_remove(struct phy_device *phydev)
        ar8xxx_free(priv);
 }
 
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0)
 static int
 ar8xxx_phy_soft_reset(struct phy_device *phydev)
 {
        /* we don't need an extra reset */
        return 0;
 }
-#endif
 
-static struct phy_driver ar8xxx_phy_driver = {
-       .phy_id         = 0x004d0000,
-       .name           = "Atheros AR8216/AR8236/AR8316",
-       .phy_id_mask    = 0xffff0000,
-       .features       = PHY_BASIC_FEATURES,
-       .probe          = ar8xxx_phy_probe,
-       .remove         = ar8xxx_phy_remove,
-       .detach         = ar8xxx_phy_detach,
-       .config_init    = ar8xxx_phy_config_init,
-       .config_aneg    = ar8xxx_phy_config_aneg,
-       .read_status    = ar8xxx_phy_read_status,
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0)
-       .soft_reset     = ar8xxx_phy_soft_reset,
-#endif
-       .driver         = { .owner = THIS_MODULE },
+static struct phy_driver ar8xxx_phy_driver[] = {
+       {
+               .phy_id         = 0x004d0000,
+               .name           = "Atheros AR8216/AR8236/AR8316",
+               .phy_id_mask    = 0xffff0000,
+               .features       = PHY_BASIC_FEATURES,
+               .probe          = ar8xxx_phy_probe,
+               .remove         = ar8xxx_phy_remove,
+               .detach         = ar8xxx_phy_detach,
+               .config_init    = ar8xxx_phy_config_init,
+               .config_aneg    = ar8xxx_phy_config_aneg,
+               .read_status    = ar8xxx_phy_read_status,
+               .soft_reset     = ar8xxx_phy_soft_reset,
+       }
 };
 
-int __init
-ar8xxx_init(void)
-{
-       return phy_driver_register(&ar8xxx_phy_driver);
-}
-
-void __exit
-ar8xxx_exit(void)
-{
-       phy_driver_unregister(&ar8xxx_phy_driver);
-}
-
-module_init(ar8xxx_init);
-module_exit(ar8xxx_exit);
+module_phy_driver(ar8xxx_phy_driver);
 MODULE_LICENSE("GPL");
-