net: dsa: add includes for ethtool and phy_fixed definitions
[firefly-linux-kernel-4.4.55.git] / net / dsa / slave.c
index 45a1e34c89e0d975dd9f361a73a5617d69a10301..a851e9f1411807f849567623483b56c4479695a0 100644 (file)
@@ -9,9 +9,11 @@
  */
 
 #include <linux/list.h>
-#include <linux/netdevice.h>
 #include <linux/etherdevice.h>
 #include <linux/phy.h>
+#include <linux/phy_fixed.h>
+#include <linux/of_net.h>
+#include <linux/of_mdio.h>
 #include "dsa_priv.h"
 
 /* slave mii_bus handling ***************************************************/
@@ -19,7 +21,7 @@ static int dsa_slave_phy_read(struct mii_bus *bus, int addr, int reg)
 {
        struct dsa_switch *ds = bus->priv;
 
-       if (ds->phys_port_mask & (1 << addr))
+       if (ds->phys_mii_mask & (1 << addr))
                return ds->drv->phy_read(ds, addr, reg);
 
        return 0xffff;
@@ -29,7 +31,7 @@ static int dsa_slave_phy_write(struct mii_bus *bus, int addr, int reg, u16 val)
 {
        struct dsa_switch *ds = bus->priv;
 
-       if (ds->phys_port_mask & (1 << addr))
+       if (ds->phys_mii_mask & (1 << addr))
                return ds->drv->phy_write(ds, addr, reg, val);
 
        return 0;
@@ -43,7 +45,7 @@ void dsa_slave_mii_bus_init(struct dsa_switch *ds)
        ds->slave_mii_bus->write = dsa_slave_phy_write;
        snprintf(ds->slave_mii_bus->id, MII_BUS_ID_SIZE, "dsa-%d:%.2x",
                        ds->index, ds->pd->sw_addr);
-       ds->slave_mii_bus->parent = &ds->master_mii_bus->dev;
+       ds->slave_mii_bus->parent = ds->master_dev;
 }
 
 
@@ -61,6 +63,7 @@ static int dsa_slave_open(struct net_device *dev)
 {
        struct dsa_slave_priv *p = netdev_priv(dev);
        struct net_device *master = p->parent->dst->master_netdev;
+       struct dsa_switch *ds = p->parent;
        int err;
 
        if (!(master->flags & IFF_UP))
@@ -83,8 +86,20 @@ static int dsa_slave_open(struct net_device *dev)
                        goto clear_allmulti;
        }
 
+       if (ds->drv->port_enable) {
+               err = ds->drv->port_enable(ds, p->port, p->phy);
+               if (err)
+                       goto clear_promisc;
+       }
+
+       if (p->phy)
+               phy_start(p->phy);
+
        return 0;
 
+clear_promisc:
+       if (dev->flags & IFF_PROMISC)
+               dev_set_promiscuity(master, 0);
 clear_allmulti:
        if (dev->flags & IFF_ALLMULTI)
                dev_set_allmulti(master, -1);
@@ -99,6 +114,10 @@ static int dsa_slave_close(struct net_device *dev)
 {
        struct dsa_slave_priv *p = netdev_priv(dev);
        struct net_device *master = p->parent->dst->master_netdev;
+       struct dsa_switch *ds = p->parent;
+
+       if (p->phy)
+               phy_stop(p->phy);
 
        dev_mc_unsync(master, dev);
        dev_uc_unsync(master, dev);
@@ -110,6 +129,9 @@ static int dsa_slave_close(struct net_device *dev)
        if (!ether_addr_equal(dev->dev_addr, master->dev_addr))
                dev_uc_del(master, dev->dev_addr);
 
+       if (ds->drv->port_disable)
+               ds->drv->port_disable(ds, p->port, p->phy);
+
        return 0;
 }
 
@@ -171,6 +193,24 @@ static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
        return -EOPNOTSUPP;
 }
 
+static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+
+       return p->xmit(skb, dev);
+}
+
+static netdev_tx_t dsa_slave_notag_xmit(struct sk_buff *skb,
+                                       struct net_device *dev)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+
+       skb->dev = p->parent->dst->master_netdev;
+       dev_queue_xmit(skb);
+
+       return NETDEV_TX_OK;
+}
+
 
 /* ethtool operations *******************************************************/
 static int
@@ -282,6 +322,65 @@ static int dsa_slave_get_sset_count(struct net_device *dev, int sset)
        return -EOPNOTSUPP;
 }
 
+static void dsa_slave_get_wol(struct net_device *dev, struct ethtool_wolinfo *w)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+
+       if (ds->drv->get_wol)
+               ds->drv->get_wol(ds, p->port, w);
+}
+
+static int dsa_slave_set_wol(struct net_device *dev, struct ethtool_wolinfo *w)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+       int ret = -EOPNOTSUPP;
+
+       if (ds->drv->set_wol)
+               ret = ds->drv->set_wol(ds, p->port, w);
+
+       return ret;
+}
+
+static int dsa_slave_set_eee(struct net_device *dev, struct ethtool_eee *e)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+       int ret;
+
+       if (!ds->drv->set_eee)
+               return -EOPNOTSUPP;
+
+       ret = ds->drv->set_eee(ds, p->port, p->phy, e);
+       if (ret)
+               return ret;
+
+       if (p->phy)
+               ret = phy_ethtool_set_eee(p->phy, e);
+
+       return ret;
+}
+
+static int dsa_slave_get_eee(struct net_device *dev, struct ethtool_eee *e)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+       int ret;
+
+       if (!ds->drv->get_eee)
+               return -EOPNOTSUPP;
+
+       ret = ds->drv->get_eee(ds, p->port, e);
+       if (ret)
+               return ret;
+
+       if (p->phy)
+               ret = phy_ethtool_get_eee(p->phy, e);
+
+       return ret;
+}
+
 static const struct ethtool_ops dsa_slave_ethtool_ops = {
        .get_settings           = dsa_slave_get_settings,
        .set_settings           = dsa_slave_set_settings,
@@ -291,46 +390,143 @@ static const struct ethtool_ops dsa_slave_ethtool_ops = {
        .get_strings            = dsa_slave_get_strings,
        .get_ethtool_stats      = dsa_slave_get_ethtool_stats,
        .get_sset_count         = dsa_slave_get_sset_count,
+       .set_wol                = dsa_slave_set_wol,
+       .get_wol                = dsa_slave_get_wol,
+       .set_eee                = dsa_slave_set_eee,
+       .get_eee                = dsa_slave_get_eee,
 };
 
-#ifdef CONFIG_NET_DSA_TAG_DSA
-static const struct net_device_ops dsa_netdev_ops = {
+static const struct net_device_ops dsa_slave_netdev_ops = {
        .ndo_init               = dsa_slave_init,
        .ndo_open               = dsa_slave_open,
        .ndo_stop               = dsa_slave_close,
-       .ndo_start_xmit         = dsa_xmit,
+       .ndo_start_xmit         = dsa_slave_xmit,
        .ndo_change_rx_flags    = dsa_slave_change_rx_flags,
        .ndo_set_rx_mode        = dsa_slave_set_rx_mode,
        .ndo_set_mac_address    = dsa_slave_set_mac_address,
        .ndo_do_ioctl           = dsa_slave_ioctl,
 };
-#endif
-#ifdef CONFIG_NET_DSA_TAG_EDSA
-static const struct net_device_ops edsa_netdev_ops = {
-       .ndo_init               = dsa_slave_init,
-       .ndo_open               = dsa_slave_open,
-       .ndo_stop               = dsa_slave_close,
-       .ndo_start_xmit         = edsa_xmit,
-       .ndo_change_rx_flags    = dsa_slave_change_rx_flags,
-       .ndo_set_rx_mode        = dsa_slave_set_rx_mode,
-       .ndo_set_mac_address    = dsa_slave_set_mac_address,
-       .ndo_do_ioctl           = dsa_slave_ioctl,
-};
-#endif
-#ifdef CONFIG_NET_DSA_TAG_TRAILER
-static const struct net_device_ops trailer_netdev_ops = {
-       .ndo_init               = dsa_slave_init,
-       .ndo_open               = dsa_slave_open,
-       .ndo_stop               = dsa_slave_close,
-       .ndo_start_xmit         = trailer_xmit,
-       .ndo_change_rx_flags    = dsa_slave_change_rx_flags,
-       .ndo_set_rx_mode        = dsa_slave_set_rx_mode,
-       .ndo_set_mac_address    = dsa_slave_set_mac_address,
-       .ndo_do_ioctl           = dsa_slave_ioctl,
-};
-#endif
+
+static void dsa_slave_adjust_link(struct net_device *dev)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+       unsigned int status_changed = 0;
+
+       if (p->old_link != p->phy->link) {
+               status_changed = 1;
+               p->old_link = p->phy->link;
+       }
+
+       if (p->old_duplex != p->phy->duplex) {
+               status_changed = 1;
+               p->old_duplex = p->phy->duplex;
+       }
+
+       if (p->old_pause != p->phy->pause) {
+               status_changed = 1;
+               p->old_pause = p->phy->pause;
+       }
+
+       if (ds->drv->adjust_link && status_changed)
+               ds->drv->adjust_link(ds, p->port, p->phy);
+
+       if (status_changed)
+               phy_print_status(p->phy);
+}
+
+static int dsa_slave_fixed_link_update(struct net_device *dev,
+                                      struct fixed_phy_status *status)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+
+       if (ds->drv->fixed_link_update)
+               ds->drv->fixed_link_update(ds, p->port, status);
+
+       return 0;
+}
 
 /* slave device setup *******************************************************/
+static void dsa_slave_phy_setup(struct dsa_slave_priv *p,
+                               struct net_device *slave_dev)
+{
+       struct dsa_switch *ds = p->parent;
+       struct dsa_chip_data *cd = ds->pd;
+       struct device_node *phy_dn, *port_dn;
+       bool phy_is_fixed = false;
+       u32 phy_flags = 0;
+       int ret;
+
+       port_dn = cd->port_dn[p->port];
+       p->phy_interface = of_get_phy_mode(port_dn);
+
+       phy_dn = of_parse_phandle(port_dn, "phy-handle", 0);
+       if (of_phy_is_fixed_link(port_dn)) {
+               /* In the case of a fixed PHY, the DT node associated
+                * to the fixed PHY is the Port DT node
+                */
+               ret = of_phy_register_fixed_link(port_dn);
+               if (ret) {
+                       pr_err("failed to register fixed PHY\n");
+                       return;
+               }
+               phy_is_fixed = true;
+               phy_dn = port_dn;
+       }
+
+       if (ds->drv->get_phy_flags)
+               phy_flags = ds->drv->get_phy_flags(ds, p->port);
+
+       if (phy_dn)
+               p->phy = of_phy_connect(slave_dev, phy_dn,
+                                       dsa_slave_adjust_link, phy_flags,
+                                       p->phy_interface);
+
+       if (p->phy && phy_is_fixed)
+               fixed_phy_set_link_update(p->phy, dsa_slave_fixed_link_update);
+
+       /* We could not connect to a designated PHY, so use the switch internal
+        * MDIO bus instead
+        */
+       if (!p->phy)
+               p->phy = ds->slave_mii_bus->phy_map[p->port];
+       else
+               pr_info("attached PHY at address %d [%s]\n",
+                       p->phy->addr, p->phy->drv->name);
+}
+
+int dsa_slave_suspend(struct net_device *slave_dev)
+{
+       struct dsa_slave_priv *p = netdev_priv(slave_dev);
+
+       netif_device_detach(slave_dev);
+
+       if (p->phy) {
+               phy_stop(p->phy);
+               p->old_pause = -1;
+               p->old_link = -1;
+               p->old_duplex = -1;
+               phy_suspend(p->phy);
+       }
+
+       return 0;
+}
+
+int dsa_slave_resume(struct net_device *slave_dev)
+{
+       struct dsa_slave_priv *p = netdev_priv(slave_dev);
+
+       netif_device_attach(slave_dev);
+
+       if (p->phy) {
+               phy_resume(p->phy);
+               phy_start(p->phy);
+       }
+
+       return 0;
+}
+
 struct net_device *
 dsa_slave_create(struct dsa_switch *ds, struct device *parent,
                 int port, char *name)
@@ -349,35 +545,48 @@ dsa_slave_create(struct dsa_switch *ds, struct device *parent,
        slave_dev->ethtool_ops = &dsa_slave_ethtool_ops;
        eth_hw_addr_inherit(slave_dev, master);
        slave_dev->tx_queue_len = 0;
+       slave_dev->netdev_ops = &dsa_slave_netdev_ops;
+
+       SET_NETDEV_DEV(slave_dev, parent);
+       slave_dev->dev.of_node = ds->pd->port_dn[port];
+       slave_dev->vlan_features = master->vlan_features;
+
+       p = netdev_priv(slave_dev);
+       p->dev = slave_dev;
+       p->parent = ds;
+       p->port = port;
 
        switch (ds->dst->tag_protocol) {
 #ifdef CONFIG_NET_DSA_TAG_DSA
-       case htons(ETH_P_DSA):
-               slave_dev->netdev_ops = &dsa_netdev_ops;
+       case DSA_TAG_PROTO_DSA:
+               p->xmit = dsa_netdev_ops.xmit;
                break;
 #endif
 #ifdef CONFIG_NET_DSA_TAG_EDSA
-       case htons(ETH_P_EDSA):
-               slave_dev->netdev_ops = &edsa_netdev_ops;
+       case DSA_TAG_PROTO_EDSA:
+               p->xmit = edsa_netdev_ops.xmit;
                break;
 #endif
 #ifdef CONFIG_NET_DSA_TAG_TRAILER
-       case htons(ETH_P_TRAILER):
-               slave_dev->netdev_ops = &trailer_netdev_ops;
+       case DSA_TAG_PROTO_TRAILER:
+               p->xmit = trailer_netdev_ops.xmit;
+               break;
+#endif
+#ifdef CONFIG_NET_DSA_TAG_BRCM
+       case DSA_TAG_PROTO_BRCM:
+               p->xmit = brcm_netdev_ops.xmit;
                break;
 #endif
        default:
-               BUG();
+               p->xmit = dsa_slave_notag_xmit;
+               break;
        }
 
-       SET_NETDEV_DEV(slave_dev, parent);
-       slave_dev->vlan_features = master->vlan_features;
+       p->old_pause = -1;
+       p->old_link = -1;
+       p->old_duplex = -1;
 
-       p = netdev_priv(slave_dev);
-       p->dev = slave_dev;
-       p->parent = ds;
-       p->port = port;
-       p->phy = ds->slave_mii_bus->phy_map[port];
+       dsa_slave_phy_setup(p, slave_dev);
 
        ret = register_netdev(slave_dev);
        if (ret) {
@@ -390,6 +599,9 @@ dsa_slave_create(struct dsa_switch *ds, struct device *parent,
        netif_carrier_off(slave_dev);
 
        if (p->phy != NULL) {
+               if (ds->drv->get_phy_flags(ds, port))
+                       p->phy->dev_flags |= ds->drv->get_phy_flags(ds, port);
+
                phy_attach(slave_dev, dev_name(&p->phy->dev),
                           PHY_INTERFACE_MODE_GMII);
 
@@ -397,7 +609,6 @@ dsa_slave_create(struct dsa_switch *ds, struct device *parent,
                p->phy->speed = 0;
                p->phy->duplex = 0;
                p->phy->advertising = p->phy->supported | ADVERTISED_Autoneg;
-               phy_start_aneg(p->phy);
        }
 
        return slave_dev;