From f7f1de51edbdd53b09061d12758cacd9901c363e Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 24 Sep 2014 17:05:17 -0700 Subject: [PATCH 1/6] net: dsa: start and stop the PHY state machine dsa_slave_open() should start the PHY library state machine for its PHY interface, and dsa_slave_close() should stop the PHY library state machine accordingly. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- net/dsa/slave.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/dsa/slave.c b/net/dsa/slave.c index 43c1e4ade689..4392e983abda 100644 --- a/net/dsa/slave.c +++ b/net/dsa/slave.c @@ -84,6 +84,9 @@ static int dsa_slave_open(struct net_device *dev) goto clear_allmulti; } + if (p->phy) + phy_start(p->phy); + return 0; clear_allmulti: @@ -101,6 +104,9 @@ 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; + if (p->phy) + phy_stop(p->phy); + dev_mc_unsync(master, dev); dev_uc_unsync(master, dev); if (dev->flags & IFF_ALLMULTI) From b2f2af21e37f6d12bd735c27da8942331aa9b3d7 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 24 Sep 2014 17:05:18 -0700 Subject: [PATCH 2/6] net: dsa: allow enabling and disable switch ports Whenever a per-port network device is used/unused, invoke the switch driver port_enable/port_disable callbacks to allow saving as much power as possible by disabling unused parts of the switch (RX/TX logic, memory arrays, PHYs...). We supply a PHY device argument to make sure the switch driver can act on the PHY device if needed (like putting/taking the PHY out of deep low power mode). Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- include/net/dsa.h | 8 ++++++++ net/dsa/slave.c | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/net/dsa.h b/include/net/dsa.h index d8054fb4a4df..4f664fe0e42c 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -224,6 +224,14 @@ struct dsa_switch_driver { */ int (*suspend)(struct dsa_switch *ds); int (*resume)(struct dsa_switch *ds); + + /* + * Port enable/disable + */ + int (*port_enable)(struct dsa_switch *ds, int port, + struct phy_device *phy); + void (*port_disable)(struct dsa_switch *ds, int port, + struct phy_device *phy); }; void register_switch_driver(struct dsa_switch_driver *type); diff --git a/net/dsa/slave.c b/net/dsa/slave.c index 4392e983abda..182d30ae6818 100644 --- a/net/dsa/slave.c +++ b/net/dsa/slave.c @@ -62,6 +62,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)) @@ -84,11 +85,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); @@ -103,6 +113,7 @@ 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); @@ -117,6 +128,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; } From 7de1557ce7521e756974d5c28794c2375d28e3cc Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 24 Sep 2014 17:05:19 -0700 Subject: [PATCH 3/6] net: dsa: bcm_sf2: disable RGMII interface(s) when link is down When the link is down, disable the RGMII interface to conserve as much power as possible. We re-enable the RGMII interface whenever the link is detected. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- drivers/net/dsa/bcm_sf2.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c index d9b7da545063..58b8fef25b96 100644 --- a/drivers/net/dsa/bcm_sf2.c +++ b/drivers/net/dsa/bcm_sf2.c @@ -506,6 +506,15 @@ static void bcm_sf2_sw_adjust_link(struct dsa_switch *ds, int port, port_mode = EXT_REVMII; break; default: + /* All other PHYs: internal and MoCA */ + goto force_link; + } + + /* If the link is down, just disable the interface to conserve power */ + if (!phydev->link) { + reg = reg_readl(priv, REG_RGMII_CNTRL_P(port)); + reg &= ~RGMII_MODE_EN; + reg_writel(priv, reg, REG_RGMII_CNTRL_P(port)); goto force_link; } From b6d045db59210476323caef042c5b50884e4675f Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 24 Sep 2014 17:05:20 -0700 Subject: [PATCH 4/6] net: dsa: bcm_sf2: add port_enable/disable callbacks The SF2 switch driver is already architected around per-port enable/disable callbacks, so we just need a slight update to our existing bcm_sf2_port_setup() resp. bcm_sf2_port_disable() functions to be suitable as callbacks for port_enable/port_disable. We need to shuffle a little the code that does the per-port VLAN configuration/isolation since ports can now be brought up/down separately, so we need to make sure that IMP (CPU, management) port is always included in that specific port setup. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- drivers/net/dsa/bcm_sf2.c | 60 ++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c index 58b8fef25b96..634e44ee8d0d 100644 --- a/drivers/net/dsa/bcm_sf2.c +++ b/drivers/net/dsa/bcm_sf2.c @@ -135,10 +135,29 @@ static char *bcm_sf2_sw_probe(struct device *host_dev, int sw_addr) return "Broadcom Starfighter 2"; } -static void bcm_sf2_imp_setup(struct dsa_switch *ds, int port) +static void bcm_sf2_imp_vlan_setup(struct dsa_switch *ds, int cpu_port) { struct bcm_sf2_priv *priv = ds_to_priv(ds); unsigned int i; + u32 reg; + + /* Enable the IMP Port to be in the same VLAN as the other ports + * on a per-port basis such that we only have Port i and IMP in + * the same VLAN. + */ + for (i = 0; i < priv->hw_params.num_ports; i++) { + if (!((1 << i) & ds->phys_port_mask)) + continue; + + reg = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(i)); + reg |= (1 << cpu_port); + core_writel(priv, reg, CORE_PORT_VLAN_CTL_PORT(i)); + } +} + +static void bcm_sf2_imp_setup(struct dsa_switch *ds, int port) +{ + struct bcm_sf2_priv *priv = ds_to_priv(ds); u32 reg, val; /* Enable the port memories */ @@ -199,24 +218,13 @@ static void bcm_sf2_imp_setup(struct dsa_switch *ds, int port) reg = core_readl(priv, CORE_STS_OVERRIDE_IMP); reg |= (MII_SW_OR | LINK_STS); core_writel(priv, reg, CORE_STS_OVERRIDE_IMP); - - /* Enable the IMP Port to be in the same VLAN as the other ports - * on a per-port basis such that we only have Port i and IMP in - * the same VLAN. - */ - for (i = 0; i < priv->hw_params.num_ports; i++) { - if (!((1 << i) & ds->phys_port_mask)) - continue; - - reg = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(i)); - reg |= (1 << port); - core_writel(priv, reg, CORE_PORT_VLAN_CTL_PORT(i)); - } } -static void bcm_sf2_port_setup(struct dsa_switch *ds, int port) +static int bcm_sf2_port_setup(struct dsa_switch *ds, int port, + struct phy_device *phy) { struct bcm_sf2_priv *priv = ds_to_priv(ds); + s8 cpu_port = ds->dst[ds->index].cpu_port; u32 reg; /* Clear the memory power down */ @@ -236,9 +244,14 @@ static void bcm_sf2_port_setup(struct dsa_switch *ds, int port) reg &= ~PORT_VLAN_CTRL_MASK; reg |= (1 << port); core_writel(priv, reg, CORE_PORT_VLAN_CTL_PORT(port)); + + bcm_sf2_imp_vlan_setup(ds, cpu_port); + + return 0; } -static void bcm_sf2_port_disable(struct dsa_switch *ds, int port) +static void bcm_sf2_port_disable(struct dsa_switch *ds, int port, + struct phy_device *phy) { struct bcm_sf2_priv *priv = ds_to_priv(ds); u32 off, reg; @@ -246,6 +259,11 @@ static void bcm_sf2_port_disable(struct dsa_switch *ds, int port) if (priv->wol_ports_mask & (1 << port)) return; + if (port == 7) { + intrl2_1_mask_set(priv, P_IRQ_MASK(P7_IRQ_OFF)); + intrl2_1_writel(priv, P_IRQ_MASK(P7_IRQ_OFF), INTRL2_CPU_CLEAR); + } + if (dsa_is_cpu_port(ds, port)) off = CORE_IMP_CTL; else @@ -363,11 +381,11 @@ static int bcm_sf2_sw_setup(struct dsa_switch *ds) for (port = 0; port < priv->hw_params.num_ports; port++) { /* IMP port receives special treatment */ if ((1 << port) & ds->phys_port_mask) - bcm_sf2_port_setup(ds, port); + bcm_sf2_port_setup(ds, port, NULL); else if (dsa_is_cpu_port(ds, port)) bcm_sf2_imp_setup(ds, port); else - bcm_sf2_port_disable(ds, port); + bcm_sf2_port_disable(ds, port, NULL); } /* Include the pseudo-PHY address and the broadcast PHY address to @@ -638,7 +656,7 @@ static int bcm_sf2_sw_suspend(struct dsa_switch *ds) 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); + bcm_sf2_port_disable(ds, port, NULL); } return 0; @@ -694,7 +712,7 @@ static int bcm_sf2_sw_resume(struct dsa_switch *ds) for (port = 0; port < DSA_MAX_PORTS; port++) { if ((1 << port) & ds->phys_port_mask) - bcm_sf2_port_setup(ds, port); + bcm_sf2_port_setup(ds, port, NULL); else if (dsa_is_cpu_port(ds, port)) bcm_sf2_imp_setup(ds, port); } @@ -772,6 +790,8 @@ static struct dsa_switch_driver bcm_sf2_switch_driver = { .resume = bcm_sf2_sw_resume, .get_wol = bcm_sf2_sw_get_wol, .set_wol = bcm_sf2_sw_set_wol, + .port_enable = bcm_sf2_port_setup, + .port_disable = bcm_sf2_port_disable, }; static int __init bcm_sf2_init(void) From 7905288f093ad924004609bb89a7ce1597892726 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 24 Sep 2014 17:05:21 -0700 Subject: [PATCH 5/6] net: dsa: allow switches driver to implement get/set EEE Allow switches driver to query and enable/disable EEE on a per-port basis by implementing the ethtool_{get,set}_eee settings and delegating these operations to the switch driver. set_eee() will need to coordinate with the PHY driver to make sure that EEE is enabled, the link-partner supports it and the auto-negotiation result is satisfactory. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- include/net/dsa.h | 9 +++++++++ net/dsa/slave.c | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/include/net/dsa.h b/include/net/dsa.h index 4f664fe0e42c..58ad8c6492db 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -232,6 +232,15 @@ struct dsa_switch_driver { struct phy_device *phy); void (*port_disable)(struct dsa_switch *ds, int port, struct phy_device *phy); + + /* + * EEE setttings + */ + int (*set_eee)(struct dsa_switch *ds, int port, + struct phy_device *phydev, + struct ethtool_eee *e); + int (*get_eee)(struct dsa_switch *ds, int port, + struct ethtool_eee *e); }; void register_switch_driver(struct dsa_switch_driver *type); diff --git a/net/dsa/slave.c b/net/dsa/slave.c index 182d30ae6818..36953c84ff2d 100644 --- a/net/dsa/slave.c +++ b/net/dsa/slave.c @@ -342,6 +342,44 @@ static int dsa_slave_set_wol(struct net_device *dev, struct ethtool_wolinfo *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, @@ -353,6 +391,8 @@ static const struct ethtool_ops dsa_slave_ethtool_ops = { .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, }; static const struct net_device_ops dsa_slave_netdev_ops = { From 450b05c15f9c776996f9627c7b4f1d38b6e6f4a0 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 24 Sep 2014 17:05:22 -0700 Subject: [PATCH 6/6] net: dsa: bcm_sf2: add support for controlling EEE When EEE is enabled, negotiate this feature with the PHY and make sure that the capability checking, local EEE advertisement, link partner EEE advertisement and auto-negotiation resolution returned by phy_init_eee() is positive, and enable EEE at the switch level. While querying the current EEE settings, verify the low-power indication and indicate its status. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- drivers/net/dsa/bcm_sf2.c | 73 ++++++++++++++++++++++++++++++++++ drivers/net/dsa/bcm_sf2.h | 3 ++ drivers/net/dsa/bcm_sf2_regs.h | 3 ++ 3 files changed, 79 insertions(+) diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c index 634e44ee8d0d..b9625968daac 100644 --- a/drivers/net/dsa/bcm_sf2.c +++ b/drivers/net/dsa/bcm_sf2.c @@ -220,6 +220,19 @@ static void bcm_sf2_imp_setup(struct dsa_switch *ds, int port) core_writel(priv, reg, CORE_STS_OVERRIDE_IMP); } +static void bcm_sf2_eee_enable_set(struct dsa_switch *ds, int port, bool enable) +{ + struct bcm_sf2_priv *priv = ds_to_priv(ds); + u32 reg; + + reg = core_readl(priv, CORE_EEE_EN_CTRL); + if (enable) + reg |= 1 << port; + else + reg &= ~(1 << port); + core_writel(priv, reg, CORE_EEE_EN_CTRL); +} + static int bcm_sf2_port_setup(struct dsa_switch *ds, int port, struct phy_device *phy) { @@ -247,6 +260,10 @@ static int bcm_sf2_port_setup(struct dsa_switch *ds, int port, bcm_sf2_imp_vlan_setup(ds, cpu_port); + /* If EEE was enabled, restore it */ + if (priv->port_sts[port].eee.eee_enabled) + bcm_sf2_eee_enable_set(ds, port, true); + return 0; } @@ -279,6 +296,60 @@ static void bcm_sf2_port_disable(struct dsa_switch *ds, int port, core_writel(priv, reg, CORE_MEM_PSM_VDD_CTRL); } +/* Returns 0 if EEE was not enabled, or 1 otherwise + */ +static int bcm_sf2_eee_init(struct dsa_switch *ds, int port, + struct phy_device *phy) +{ + struct bcm_sf2_priv *priv = ds_to_priv(ds); + struct ethtool_eee *p = &priv->port_sts[port].eee; + int ret; + + p->supported = (SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full); + + ret = phy_init_eee(phy, 0); + if (ret) + return 0; + + bcm_sf2_eee_enable_set(ds, port, true); + + return 1; +} + +static int bcm_sf2_sw_get_eee(struct dsa_switch *ds, int port, + struct ethtool_eee *e) +{ + struct bcm_sf2_priv *priv = ds_to_priv(ds); + struct ethtool_eee *p = &priv->port_sts[port].eee; + u32 reg; + + reg = core_readl(priv, CORE_EEE_LPI_INDICATE); + e->eee_enabled = p->eee_enabled; + e->eee_active = !!(reg & (1 << port)); + + return 0; +} + +static int bcm_sf2_sw_set_eee(struct dsa_switch *ds, int port, + struct phy_device *phydev, + struct ethtool_eee *e) +{ + struct bcm_sf2_priv *priv = ds_to_priv(ds); + struct ethtool_eee *p = &priv->port_sts[port].eee; + + p->eee_enabled = e->eee_enabled; + + if (!p->eee_enabled) { + bcm_sf2_eee_enable_set(ds, port, false); + } else { + p->eee_enabled = bcm_sf2_eee_init(ds, port, phydev); + if (!p->eee_enabled) + return -EOPNOTSUPP; + } + + return 0; +} + static irqreturn_t bcm_sf2_switch_0_isr(int irq, void *dev_id) { struct bcm_sf2_priv *priv = dev_id; @@ -792,6 +863,8 @@ static struct dsa_switch_driver bcm_sf2_switch_driver = { .set_wol = bcm_sf2_sw_set_wol, .port_enable = bcm_sf2_port_setup, .port_disable = bcm_sf2_port_disable, + .get_eee = bcm_sf2_sw_get_eee, + .set_eee = bcm_sf2_sw_set_eee, }; static int __init bcm_sf2_init(void) diff --git a/drivers/net/dsa/bcm_sf2.h b/drivers/net/dsa/bcm_sf2.h index 8fd6c1451a84..ee9f650d5026 100644 --- a/drivers/net/dsa/bcm_sf2.h +++ b/drivers/net/dsa/bcm_sf2.h @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -43,6 +44,8 @@ struct bcm_sf2_hw_params { struct bcm_sf2_port_status { unsigned int link; + + struct ethtool_eee eee; }; struct bcm_sf2_priv { diff --git a/drivers/net/dsa/bcm_sf2_regs.h b/drivers/net/dsa/bcm_sf2_regs.h index c65f138c777f..1bb49cb699ab 100644 --- a/drivers/net/dsa/bcm_sf2_regs.h +++ b/drivers/net/dsa/bcm_sf2_regs.h @@ -225,4 +225,7 @@ #define CORE_PORT_VLAN_CTL_PORT(x) (0xc400 + ((x) * 0x8)) #define PORT_VLAN_CTRL_MASK 0x1ff +#define CORE_EEE_EN_CTRL 0x24800 +#define CORE_EEE_LPI_INDICATE 0x24810 + #endif /* __BCM_SF2_REGS_H */