diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c index d9b7da545063c9b921e524173ad00d688aa46a2e..b9625968daacc0eb89c0f7371a3a4e70242f95ce 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,26 @@ 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; +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_PORT_VLAN_CTL_PORT(i)); - reg |= (1 << port); - core_writel(priv, reg, CORE_PORT_VLAN_CTL_PORT(i)); - } + 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 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 +257,18 @@ 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); + + /* If EEE was enabled, restore it */ + if (priv->port_sts[port].eee.eee_enabled) + bcm_sf2_eee_enable_set(ds, port, true); + + 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 +276,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 @@ -261,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; @@ -363,11 +452,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 @@ -506,6 +595,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; } @@ -629,7 +727,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; @@ -685,7 +783,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); } @@ -763,6 +861,10 @@ 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, + .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 8fd6c1451a84a5903776cbedd4bbec743a3c408d..ee9f650d50264bb74771783b65e860b2aabb3d46 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 c65f138c777f5bd6a24495f2a40bdd674e454a2f..1bb49cb699ab02a5af7ef1de66ab6bbed0aa47ae 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 */ diff --git a/include/net/dsa.h b/include/net/dsa.h index d8054fb4a4dffaf45d3864b938d99ffb6c32b6bd..58ad8c6492dba56360b9cad1c81d4061526721c9 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -224,6 +224,23 @@ 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); + + /* + * 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 43c1e4ade6892356fbc13ff5c6fefec4ea776f21..36953c84ff2d44e488ba79fd3125cf7df2abdda6 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,8 +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); @@ -100,6 +113,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); @@ -111,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; } @@ -322,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, @@ -333,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 = {