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>
This commit is contained in:
commit
61a3bd1492
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user