net: dsa: allow switch drivers to implement suspend/resume hooks
authorFlorian Fainelli <f.fainelli@gmail.com>
Fri, 19 Sep 2014 00:31:22 +0000 (17:31 -0700)
committerDavid S. Miller <davem@davemloft.net>
Mon, 22 Sep 2014 18:41:23 +0000 (14:41 -0400)
Add an abstraction layer to suspend/resume switch devices, doing the
following split:

- suspend/resume the slave network devices and their corresponding PHY
  devices
- suspend/resume the switch hardware using switch driver callbacks

Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/dsa.h
net/dsa/dsa.c
net/dsa/dsa_priv.h
net/dsa/slave.c

index e020b8a12b7d1091c05bd63d89c646315000c0b9..846dce4abaa5db7f6d661d62d84029138a3f292d 100644 (file)
@@ -210,6 +210,12 @@ struct dsa_switch_driver {
        void    (*get_ethtool_stats)(struct dsa_switch *ds,
                                     int port, uint64_t *data);
        int     (*get_sset_count)(struct dsa_switch *ds);
+
+       /*
+        * Suspend and resume
+        */
+       int     (*suspend)(struct dsa_switch *ds);
+       int     (*resume)(struct dsa_switch *ds);
 };
 
 void register_switch_driver(struct dsa_switch_driver *type);
index 6e40928ec0e76ba09cac488bc75ae92416465b1b..6905f2d84c44f48ba8611b222910a3b06a6464f6 100644 (file)
@@ -238,6 +238,49 @@ static void dsa_switch_destroy(struct dsa_switch *ds)
 {
 }
 
+static int dsa_switch_suspend(struct dsa_switch *ds)
+{
+       int i, ret = 0;
+
+       /* Suspend slave network devices */
+       for (i = 0; i < DSA_MAX_PORTS; i++) {
+               if (!(ds->phys_port_mask & (1 << i)))
+                       continue;
+
+               ret = dsa_slave_suspend(ds->ports[i]);
+               if (ret)
+                       return ret;
+       }
+
+       if (ds->drv->suspend)
+               ret = ds->drv->suspend(ds);
+
+       return ret;
+}
+
+static int dsa_switch_resume(struct dsa_switch *ds)
+{
+       int i, ret = 0;
+
+       if (ds->drv->resume)
+               ret = ds->drv->resume(ds);
+
+       if (ret)
+               return ret;
+
+       /* Resume slave network devices */
+       for (i = 0; i < DSA_MAX_PORTS; i++) {
+               if (!(ds->phys_port_mask & (1 << i)))
+                       continue;
+
+               ret = dsa_slave_resume(ds->ports[i]);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
 
 /* link polling *************************************************************/
 static void dsa_link_poll_work(struct work_struct *ugly)
@@ -650,6 +693,42 @@ static struct packet_type dsa_pack_type __read_mostly = {
        .func   = dsa_switch_rcv,
 };
 
+#ifdef CONFIG_PM_SLEEP
+static int dsa_suspend(struct device *d)
+{
+       struct platform_device *pdev = to_platform_device(d);
+       struct dsa_switch_tree *dst = platform_get_drvdata(pdev);
+       int i, ret = 0;
+
+       for (i = 0; i < dst->pd->nr_chips; i++) {
+               struct dsa_switch *ds = dst->ds[i];
+
+               if (ds != NULL)
+                       ret = dsa_switch_suspend(ds);
+       }
+
+       return ret;
+}
+
+static int dsa_resume(struct device *d)
+{
+       struct platform_device *pdev = to_platform_device(d);
+       struct dsa_switch_tree *dst = platform_get_drvdata(pdev);
+       int i, ret = 0;
+
+       for (i = 0; i < dst->pd->nr_chips; i++) {
+               struct dsa_switch *ds = dst->ds[i];
+
+               if (ds != NULL)
+                       ret = dsa_switch_resume(ds);
+       }
+
+       return ret;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(dsa_pm_ops, dsa_suspend, dsa_resume);
+
 static const struct of_device_id dsa_of_match_table[] = {
        { .compatible = "brcm,bcm7445-switch-v4.0" },
        { .compatible = "marvell,dsa", },
@@ -665,6 +744,7 @@ static struct platform_driver dsa_driver = {
                .name   = "dsa",
                .owner  = THIS_MODULE,
                .of_match_table = dsa_of_match_table,
+               .pm     = &dsa_pm_ops,
        },
 };
 
index f90899e8ab5af87fbbd60cd199269b4944f0d2cb..dc9756d3154c7c81a1f8bd2b3124de36f0a97963 100644 (file)
@@ -56,6 +56,8 @@ void dsa_slave_mii_bus_init(struct dsa_switch *ds);
 struct net_device *dsa_slave_create(struct dsa_switch *ds,
                                    struct device *parent,
                                    int port, char *name);
+int dsa_slave_suspend(struct net_device *slave_dev);
+int dsa_slave_resume(struct net_device *slave_dev);
 
 /* tag_dsa.c */
 extern const struct dsa_device_ops dsa_netdev_ops;
index a7997265019a8abd61bb46a50d3a157c8e1e896e..143811ef57aec14c847a44755274ca4bab8a6a21 100644 (file)
@@ -412,6 +412,37 @@ static void dsa_slave_phy_setup(struct dsa_slave_priv *p,
                        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)