aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2014-09-22 14:41:28 -0400
committerDavid S. Miller <davem@davemloft.net>2014-09-22 14:41:28 -0400
commit61a3bd1492b7b1872768798384ba6451f83c87a5 (patch)
treed1103c98dd7273538de10bea5c0e259816e8c409
parentMerge branch 'qlge' (diff)
parentnet: dsa: bcm_sf2: add support for Wake-on-LAN (diff)
downloadlinux-dev-61a3bd1492b7b1872768798384ba6451f83c87a5.tar.xz
linux-dev-61a3bd1492b7b1872768798384ba6451f83c87a5.zip
Merge branch 'dsa-suspend'
Florian Fainelli says: ==================== dsa: Broadcom SF2 suspend/resume and WoL This patch add supports for suspend/resume and configuring Wake-on-LAN for Broadcom Starfighter 2 switches. ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--drivers/net/dsa/bcm_sf2.c143
-rw-r--r--drivers/net/dsa/bcm_sf2.h3
-rw-r--r--include/net/dsa.h14
-rw-r--r--net/dsa/dsa.c80
-rw-r--r--net/dsa/dsa_priv.h2
-rw-r--r--net/dsa/slave.c54
6 files changed, 296 insertions, 0 deletions
diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c
index a97ba2548ea5..d9b7da545063 100644
--- a/drivers/net/dsa/bcm_sf2.c
+++ b/drivers/net/dsa/bcm_sf2.c
@@ -22,6 +22,7 @@
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <net/dsa.h>
+#include <linux/ethtool.h>
#include "bcm_sf2.h"
#include "bcm_sf2_regs.h"
@@ -242,6 +243,9 @@ static void bcm_sf2_port_disable(struct dsa_switch *ds, int port)
struct bcm_sf2_priv *priv = ds_to_priv(ds);
u32 off, reg;
+ if (priv->wol_ports_mask & (1 << port))
+ return;
+
if (dsa_is_cpu_port(ds, port))
off = CORE_IMP_CTL;
else
@@ -606,6 +610,141 @@ static void bcm_sf2_sw_fixed_link_update(struct dsa_switch *ds, int port,
status->pause = 1;
}
+static int bcm_sf2_sw_suspend(struct dsa_switch *ds)
+{
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
+ unsigned int port;
+
+ intrl2_0_writel(priv, 0xffffffff, INTRL2_CPU_MASK_SET);
+ intrl2_0_writel(priv, 0xffffffff, INTRL2_CPU_CLEAR);
+ intrl2_0_writel(priv, 0, INTRL2_CPU_MASK_CLEAR);
+ intrl2_1_writel(priv, 0xffffffff, INTRL2_CPU_MASK_SET);
+ intrl2_1_writel(priv, 0xffffffff, INTRL2_CPU_CLEAR);
+ intrl2_1_writel(priv, 0, INTRL2_CPU_MASK_CLEAR);
+
+ /* Disable all ports physically present including the IMP
+ * port, the other ones have already been disabled during
+ * bcm_sf2_sw_setup
+ */
+ for (port = 0; port < DSA_MAX_PORTS; port++) {
+ if ((1 << port) & ds->phys_port_mask ||
+ dsa_is_cpu_port(ds, port))
+ bcm_sf2_port_disable(ds, port);
+ }
+
+ return 0;
+}
+
+static int bcm_sf2_sw_rst(struct bcm_sf2_priv *priv)
+{
+ unsigned int timeout = 1000;
+ u32 reg;
+
+ reg = core_readl(priv, CORE_WATCHDOG_CTRL);
+ reg |= SOFTWARE_RESET | EN_CHIP_RST | EN_SW_RESET;
+ core_writel(priv, reg, CORE_WATCHDOG_CTRL);
+
+ do {
+ reg = core_readl(priv, CORE_WATCHDOG_CTRL);
+ if (!(reg & SOFTWARE_RESET))
+ break;
+
+ usleep_range(1000, 2000);
+ } while (timeout-- > 0);
+
+ if (timeout == 0)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int bcm_sf2_sw_resume(struct dsa_switch *ds)
+{
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
+ unsigned int port;
+ u32 reg;
+ int ret;
+
+ ret = bcm_sf2_sw_rst(priv);
+ if (ret) {
+ pr_err("%s: failed to software reset switch\n", __func__);
+ return ret;
+ }
+
+ /* Reinitialize the single GPHY */
+ if (priv->hw_params.num_gphy == 1) {
+ reg = reg_readl(priv, REG_SPHY_CNTRL);
+ reg |= PHY_RESET;
+ reg &= ~(EXT_PWR_DOWN | IDDQ_BIAS);
+ reg_writel(priv, reg, REG_SPHY_CNTRL);
+ udelay(21);
+ reg = reg_readl(priv, REG_SPHY_CNTRL);
+ reg &= ~PHY_RESET;
+ reg_writel(priv, reg, REG_SPHY_CNTRL);
+ }
+
+ for (port = 0; port < DSA_MAX_PORTS; port++) {
+ if ((1 << port) & ds->phys_port_mask)
+ bcm_sf2_port_setup(ds, port);
+ else if (dsa_is_cpu_port(ds, port))
+ bcm_sf2_imp_setup(ds, port);
+ }
+
+ return 0;
+}
+
+static void bcm_sf2_sw_get_wol(struct dsa_switch *ds, int port,
+ struct ethtool_wolinfo *wol)
+{
+ struct net_device *p = ds->dst[ds->index].master_netdev;
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
+ struct ethtool_wolinfo pwol;
+
+ /* Get the parent device WoL settings */
+ p->ethtool_ops->get_wol(p, &pwol);
+
+ /* Advertise the parent device supported settings */
+ wol->supported = pwol.supported;
+ memset(&wol->sopass, 0, sizeof(wol->sopass));
+
+ if (pwol.wolopts & WAKE_MAGICSECURE)
+ memcpy(&wol->sopass, pwol.sopass, sizeof(wol->sopass));
+
+ if (priv->wol_ports_mask & (1 << port))
+ wol->wolopts = pwol.wolopts;
+ else
+ wol->wolopts = 0;
+}
+
+static int bcm_sf2_sw_set_wol(struct dsa_switch *ds, int port,
+ struct ethtool_wolinfo *wol)
+{
+ struct net_device *p = ds->dst[ds->index].master_netdev;
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
+ s8 cpu_port = ds->dst[ds->index].cpu_port;
+ struct ethtool_wolinfo pwol;
+
+ p->ethtool_ops->get_wol(p, &pwol);
+ if (wol->wolopts & ~pwol.supported)
+ return -EINVAL;
+
+ if (wol->wolopts)
+ priv->wol_ports_mask |= (1 << port);
+ else
+ priv->wol_ports_mask &= ~(1 << port);
+
+ /* If we have at least one port enabled, make sure the CPU port
+ * is also enabled. If the CPU port is the last one enabled, we disable
+ * it since this configuration does not make sense.
+ */
+ if (priv->wol_ports_mask && priv->wol_ports_mask != (1 << cpu_port))
+ priv->wol_ports_mask |= (1 << cpu_port);
+ else
+ priv->wol_ports_mask &= ~(1 << cpu_port);
+
+ return p->ethtool_ops->set_wol(p, wol);
+}
+
static struct dsa_switch_driver bcm_sf2_switch_driver = {
.tag_protocol = DSA_TAG_PROTO_BRCM,
.priv_size = sizeof(struct bcm_sf2_priv),
@@ -620,6 +759,10 @@ static struct dsa_switch_driver bcm_sf2_switch_driver = {
.get_sset_count = bcm_sf2_sw_get_sset_count,
.adjust_link = bcm_sf2_sw_adjust_link,
.fixed_link_update = bcm_sf2_sw_fixed_link_update,
+ .suspend = bcm_sf2_sw_suspend,
+ .resume = bcm_sf2_sw_resume,
+ .get_wol = bcm_sf2_sw_get_wol,
+ .set_wol = bcm_sf2_sw_set_wol,
};
static int __init bcm_sf2_init(void)
diff --git a/drivers/net/dsa/bcm_sf2.h b/drivers/net/dsa/bcm_sf2.h
index d3bd52dc40d2..8fd6c1451a84 100644
--- a/drivers/net/dsa/bcm_sf2.h
+++ b/drivers/net/dsa/bcm_sf2.h
@@ -70,6 +70,9 @@ struct bcm_sf2_priv {
struct bcm_sf2_hw_params hw_params;
struct bcm_sf2_port_status port_sts[DSA_MAX_PORTS];
+
+ /* Mask of ports enabled for Wake-on-LAN */
+ u32 wol_ports_mask;
};
struct bcm_sf2_hw_stats {
diff --git a/include/net/dsa.h b/include/net/dsa.h
index e020b8a12b7d..d8054fb4a4df 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -210,6 +210,20 @@ 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);
+
+ /*
+ * ethtool Wake-on-LAN
+ */
+ void (*get_wol)(struct dsa_switch *ds, int port,
+ struct ethtool_wolinfo *w);
+ int (*set_wol)(struct dsa_switch *ds, int port,
+ struct ethtool_wolinfo *w);
+
+ /*
+ * Suspend and resume
+ */
+ int (*suspend)(struct dsa_switch *ds);
+ int (*resume)(struct dsa_switch *ds);
};
void register_switch_driver(struct dsa_switch_driver *type);
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index 6e40928ec0e7..6905f2d84c44 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -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,
},
};
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index f90899e8ab5a..dc9756d3154c 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -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;
diff --git a/net/dsa/slave.c b/net/dsa/slave.c
index a7997265019a..43c1e4ade689 100644
--- a/net/dsa/slave.c
+++ b/net/dsa/slave.c
@@ -301,6 +301,27 @@ 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 const struct ethtool_ops dsa_slave_ethtool_ops = {
.get_settings = dsa_slave_get_settings,
.set_settings = dsa_slave_set_settings,
@@ -310,6 +331,8 @@ 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,
};
static const struct net_device_ops dsa_slave_netdev_ops = {
@@ -412,6 +435,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)