From 18e21b01fb94d8f6125cf73a477fdb3f88820608 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 1 Jul 2014 21:08:36 -0700 Subject: net: systemport: update umac_enable_set to take a bitmask Quite often we need to enable either the transmitter or the receiver bits in UMAC_CMD, use umac_enable_set() to do that for us. This is a preliminary change to introduce suspend/resume support in the driver. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- drivers/net/ethernet/broadcom/bcmsysport.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bcmsysport.c b/drivers/net/ethernet/broadcom/bcmsysport.c index 141160ef249a..bbe74494c629 100644 --- a/drivers/net/ethernet/broadcom/bcmsysport.c +++ b/drivers/net/ethernet/broadcom/bcmsysport.c @@ -1236,15 +1236,15 @@ static void bcm_sysport_set_rx_mode(struct net_device *dev) } static inline void umac_enable_set(struct bcm_sysport_priv *priv, - unsigned int enable) + u32 mask, unsigned int enable) { u32 reg; reg = umac_readl(priv, UMAC_CMD); if (enable) - reg |= CMD_RX_EN | CMD_TX_EN; + reg |= mask; else - reg &= ~(CMD_RX_EN | CMD_TX_EN); + reg &= ~mask; umac_writel(priv, reg, UMAC_CMD); /* UniMAC stops on a packet boundary, wait for a full-sized packet @@ -1313,7 +1313,7 @@ static int bcm_sysport_open(struct net_device *dev) topctrl_flush(priv); /* Disable the UniMAC RX/TX */ - umac_enable_set(priv, 0); + umac_enable_set(priv, CMD_RX_EN | CMD_TX_EN, 0); /* Enable RBUF 2bytes alignment and Receive Status Block */ reg = rbuf_readl(priv, RBUF_CONTROL); @@ -1398,7 +1398,7 @@ static int bcm_sysport_open(struct net_device *dev) napi_enable(&priv->napi); /* Turn on UniMAC TX/RX */ - umac_enable_set(priv, 1); + umac_enable_set(priv, CMD_RX_EN | CMD_TX_EN, 1); phy_start(priv->phydev); @@ -1429,7 +1429,6 @@ static int bcm_sysport_stop(struct net_device *dev) { struct bcm_sysport_priv *priv = netdev_priv(dev); unsigned int i; - u32 reg; int ret; /* stop all software from updating hardware */ @@ -1444,9 +1443,7 @@ static int bcm_sysport_stop(struct net_device *dev) intrl2_1_writel(priv, 0xffffffff, INTRL2_CPU_CLEAR); /* Disable UniMAC RX */ - reg = umac_readl(priv, UMAC_CMD); - reg &= ~CMD_RX_EN; - umac_writel(priv, reg, UMAC_CMD); + umac_enable_set(priv, CMD_RX_EN, 0); ret = tdma_enable_set(priv, 0); if (ret) { @@ -1464,9 +1461,7 @@ static int bcm_sysport_stop(struct net_device *dev) } /* Disable UniMAC TX */ - reg = umac_readl(priv, UMAC_CMD); - reg &= ~CMD_TX_EN; - umac_writel(priv, reg, UMAC_CMD); + umac_enable_set(priv, CMD_TX_EN, 0); /* Free RX/TX rings SW structures */ for (i = 0; i < dev->num_tx_queues; i++) -- cgit v1.2.3-59-g8ed1b From b02e6d9ba7adf95c4d1999755aa35124d262b4f7 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 1 Jul 2014 21:08:37 -0700 Subject: net: systemport: add bcm_sysport_netif_{enable,stop} Factor common code that either enables or disables the network interface with the networking stack. We are going to reuse these functions for suspend/resume callbacks. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- drivers/net/ethernet/broadcom/bcmsysport.c | 40 ++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bcmsysport.c b/drivers/net/ethernet/broadcom/bcmsysport.c index bbe74494c629..8f6ab266b178 100644 --- a/drivers/net/ethernet/broadcom/bcmsysport.c +++ b/drivers/net/ethernet/broadcom/bcmsysport.c @@ -1295,6 +1295,22 @@ static void topctrl_flush(struct bcm_sysport_priv *priv) topctrl_writel(priv, 0, TX_FLUSH_CNTL); } +static void bcm_sysport_netif_start(struct net_device *dev) +{ + struct bcm_sysport_priv *priv = netdev_priv(dev); + + /* Enable NAPI */ + napi_enable(&priv->napi); + + phy_start(priv->phydev); + + /* Enable TX interrupts for the 32 TXQs */ + intrl2_1_mask_clear(priv, 0xffffffff); + + /* Last call before we start the real business */ + netif_tx_start_all_queues(dev); +} + static int bcm_sysport_open(struct net_device *dev) { struct bcm_sysport_priv *priv = netdev_priv(dev); @@ -1394,19 +1410,10 @@ static int bcm_sysport_open(struct net_device *dev) if (ret) goto out_clear_rx_int; - /* Enable NAPI */ - napi_enable(&priv->napi); - /* Turn on UniMAC TX/RX */ umac_enable_set(priv, CMD_RX_EN | CMD_TX_EN, 1); - phy_start(priv->phydev); - - /* Enable TX interrupts for the 32 TXQs */ - intrl2_1_mask_clear(priv, 0xffffffff); - - /* Last call before we start the real business */ - netif_tx_start_all_queues(dev); + bcm_sysport_netif_start(dev); return 0; @@ -1425,11 +1432,9 @@ out_phy_disconnect: return ret; } -static int bcm_sysport_stop(struct net_device *dev) +static void bcm_sysport_netif_stop(struct net_device *dev) { struct bcm_sysport_priv *priv = netdev_priv(dev); - unsigned int i; - int ret; /* stop all software from updating hardware */ netif_tx_stop_all_queues(dev); @@ -1441,6 +1446,15 @@ static int bcm_sysport_stop(struct net_device *dev) intrl2_0_writel(priv, 0xffffffff, INTRL2_CPU_CLEAR); intrl2_1_mask_set(priv, 0xffffffff); intrl2_1_writel(priv, 0xffffffff, INTRL2_CPU_CLEAR); +} + +static int bcm_sysport_stop(struct net_device *dev) +{ + struct bcm_sysport_priv *priv = netdev_priv(dev); + unsigned int i; + int ret; + + bcm_sysport_netif_stop(dev); /* Disable UniMAC RX */ umac_enable_set(priv, CMD_RX_EN, 0); -- cgit v1.2.3-59-g8ed1b From 40755a0fce17c39557fd55b634024082671074ed Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 1 Jul 2014 21:08:38 -0700 Subject: net: systemport: add suspend and resume support Implement the hardware recommended suspend/resume procedure for SYSTEMPORT. We leverage the previous factoring work such that we can logically break all suspend/resume operations into disctint RX and TX code paths. When the system enters S3, we will loose all register contents, so make sure that we correctly re-program all the hardware and software views of the RX & TX rings as well. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- drivers/net/ethernet/broadcom/bcmsysport.c | 164 ++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bcmsysport.c b/drivers/net/ethernet/broadcom/bcmsysport.c index 8f6ab266b178..9dabcfbfc23a 100644 --- a/drivers/net/ethernet/broadcom/bcmsysport.c +++ b/drivers/net/ethernet/broadcom/bcmsysport.c @@ -1311,11 +1311,19 @@ static void bcm_sysport_netif_start(struct net_device *dev) netif_tx_start_all_queues(dev); } +static void rbuf_init(struct bcm_sysport_priv *priv) +{ + u32 reg; + + reg = rbuf_readl(priv, RBUF_CONTROL); + reg |= RBUF_4B_ALGN | RBUF_RSB_EN; + rbuf_writel(priv, reg, RBUF_CONTROL); +} + static int bcm_sysport_open(struct net_device *dev) { struct bcm_sysport_priv *priv = netdev_priv(dev); unsigned int i; - u32 reg; int ret; /* Reset UniMAC */ @@ -1332,9 +1340,7 @@ static int bcm_sysport_open(struct net_device *dev) umac_enable_set(priv, CMD_RX_EN | CMD_TX_EN, 0); /* Enable RBUF 2bytes alignment and Receive Status Block */ - reg = rbuf_readl(priv, RBUF_CONTROL); - reg |= RBUF_4B_ALGN | RBUF_RSB_EN; - rbuf_writel(priv, reg, RBUF_CONTROL); + rbuf_init(priv); /* Set maximum frame length */ umac_writel(priv, UMAC_MAX_MTU_SIZE, UMAC_MAX_FRAME_LEN); @@ -1640,6 +1646,155 @@ static int bcm_sysport_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int bcm_sysport_suspend(struct device *d) +{ + struct net_device *dev = dev_get_drvdata(d); + struct bcm_sysport_priv *priv = netdev_priv(dev); + unsigned int i; + int ret; + u32 reg; + + if (!netif_running(dev)) + return 0; + + bcm_sysport_netif_stop(dev); + + phy_suspend(priv->phydev); + + netif_device_detach(dev); + + /* Disable UniMAC RX */ + umac_enable_set(priv, CMD_RX_EN, 0); + + ret = rdma_enable_set(priv, 0); + if (ret) { + netdev_err(dev, "RDMA timeout!\n"); + return ret; + } + + /* Disable RXCHK if enabled */ + if (priv->rx_csum_en) { + reg = rxchk_readl(priv, RXCHK_CONTROL); + reg &= ~RXCHK_EN; + rxchk_writel(priv, reg, RXCHK_CONTROL); + } + + /* Flush RX pipe */ + topctrl_writel(priv, RX_FLUSH, RX_FLUSH_CNTL); + + ret = tdma_enable_set(priv, 0); + if (ret) { + netdev_err(dev, "TDMA timeout!\n"); + return ret; + } + + /* Wait for a packet boundary */ + usleep_range(2000, 3000); + + umac_enable_set(priv, CMD_TX_EN, 0); + + topctrl_writel(priv, TX_FLUSH, TX_FLUSH_CNTL); + + /* Free RX/TX rings SW structures */ + for (i = 0; i < dev->num_tx_queues; i++) + bcm_sysport_fini_tx_ring(priv, i); + bcm_sysport_fini_rx_ring(priv); + + return 0; +} + +static int bcm_sysport_resume(struct device *d) +{ + struct net_device *dev = dev_get_drvdata(d); + struct bcm_sysport_priv *priv = netdev_priv(dev); + unsigned int i; + u32 reg; + int ret; + + if (!netif_running(dev)) + return 0; + + /* Initialize both hardware and software ring */ + for (i = 0; i < dev->num_tx_queues; i++) { + ret = bcm_sysport_init_tx_ring(priv, i); + if (ret) { + netdev_err(dev, "failed to initialize TX ring %d\n", + i); + goto out_free_tx_rings; + } + } + + /* Initialize linked-list */ + tdma_writel(priv, TDMA_LL_RAM_INIT_BUSY, TDMA_STATUS); + + /* Initialize RX ring */ + ret = bcm_sysport_init_rx_ring(priv); + if (ret) { + netdev_err(dev, "failed to initialize RX ring\n"); + goto out_free_rx_ring; + } + + netif_device_attach(dev); + + /* Enable RX interrupt and TX ring full interrupt */ + intrl2_0_mask_clear(priv, INTRL2_0_RDMA_MBDONE | INTRL2_0_TX_RING_FULL); + + /* RX pipe enable */ + topctrl_writel(priv, 0, RX_FLUSH_CNTL); + + ret = rdma_enable_set(priv, 1); + if (ret) { + netdev_err(dev, "failed to enable RDMA\n"); + goto out_free_rx_ring; + } + + /* Enable rxhck */ + if (priv->rx_csum_en) { + reg = rxchk_readl(priv, RXCHK_CONTROL); + reg |= RXCHK_EN; + rxchk_writel(priv, reg, RXCHK_CONTROL); + } + + rbuf_init(priv); + + /* Set maximum frame length */ + umac_writel(priv, UMAC_MAX_MTU_SIZE, UMAC_MAX_FRAME_LEN); + + /* Set MAC address */ + umac_set_hw_addr(priv, dev->dev_addr); + + umac_enable_set(priv, CMD_RX_EN, 1); + + /* TX pipe enable */ + topctrl_writel(priv, 0, TX_FLUSH_CNTL); + + umac_enable_set(priv, CMD_TX_EN, 1); + + ret = tdma_enable_set(priv, 1); + if (ret) { + netdev_err(dev, "TDMA timeout!\n"); + goto out_free_rx_ring; + } + + phy_resume(priv->phydev); + + bcm_sysport_netif_start(dev); + + return 0; + +out_free_rx_ring: + bcm_sysport_fini_rx_ring(priv); +out_free_tx_rings: + for (i = 0; i < dev->num_tx_queues; i++) + bcm_sysport_fini_tx_ring(priv, i); + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(bcm_sysport_pm_ops, + bcm_sysport_suspend, bcm_sysport_resume); + static const struct of_device_id bcm_sysport_of_match[] = { { .compatible = "brcm,systemport-v1.00" }, { .compatible = "brcm,systemport" }, @@ -1653,6 +1808,7 @@ static struct platform_driver bcm_sysport_driver = { .name = "brcm-systemport", .owner = THIS_MODULE, .of_match_table = bcm_sysport_of_match, + .pm = &bcm_sysport_pm_ops, }, }; module_platform_driver(bcm_sysport_driver); -- cgit v1.2.3-59-g8ed1b From 9d34c1cb01ddef8e99c7855513a578c066f8300f Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 1 Jul 2014 21:08:39 -0700 Subject: net: systemport: rename rx_csum_en to rx_chk_en This boolean tells us whether we are using the RXCHK hardware block, so use a variable name that reflects that. RXCHK might be used in the future to implement Wake-on-LAN using ARP or unicast packets. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- drivers/net/ethernet/broadcom/bcmsysport.c | 10 +++++----- drivers/net/ethernet/broadcom/bcmsysport.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bcmsysport.c b/drivers/net/ethernet/broadcom/bcmsysport.c index 9dabcfbfc23a..f00793e45330 100644 --- a/drivers/net/ethernet/broadcom/bcmsysport.c +++ b/drivers/net/ethernet/broadcom/bcmsysport.c @@ -124,9 +124,9 @@ static int bcm_sysport_set_rx_csum(struct net_device *dev, struct bcm_sysport_priv *priv = netdev_priv(dev); u32 reg; - priv->rx_csum_en = !!(wanted & NETIF_F_RXCSUM); + priv->rx_chk_en = !!(wanted & NETIF_F_RXCSUM); reg = rxchk_readl(priv, RXCHK_CONTROL); - if (priv->rx_csum_en) + if (priv->rx_chk_en) reg |= RXCHK_EN; else reg &= ~RXCHK_EN; @@ -134,7 +134,7 @@ static int bcm_sysport_set_rx_csum(struct net_device *dev, /* If UniMAC forwards CRC, we need to skip over it to get * a valid CHK bit to be set in the per-packet status word */ - if (priv->rx_csum_en && priv->crc_fwd) + if (priv->rx_chk_en && priv->crc_fwd) reg |= RXCHK_SKIP_FCS; else reg &= ~RXCHK_SKIP_FCS; @@ -1674,7 +1674,7 @@ static int bcm_sysport_suspend(struct device *d) } /* Disable RXCHK if enabled */ - if (priv->rx_csum_en) { + if (priv->rx_chk_en) { reg = rxchk_readl(priv, RXCHK_CONTROL); reg &= ~RXCHK_EN; rxchk_writel(priv, reg, RXCHK_CONTROL); @@ -1750,7 +1750,7 @@ static int bcm_sysport_resume(struct device *d) } /* Enable rxhck */ - if (priv->rx_csum_en) { + if (priv->rx_chk_en) { reg = rxchk_readl(priv, RXCHK_CONTROL); reg |= RXCHK_EN; rxchk_writel(priv, reg, RXCHK_CONTROL); diff --git a/drivers/net/ethernet/broadcom/bcmsysport.h b/drivers/net/ethernet/broadcom/bcmsysport.h index 281c08246037..20ea7162478f 100644 --- a/drivers/net/ethernet/broadcom/bcmsysport.h +++ b/drivers/net/ethernet/broadcom/bcmsysport.h @@ -664,7 +664,7 @@ struct bcm_sysport_priv { int old_duplex; /* Misc fields */ - unsigned int rx_csum_en:1; + unsigned int rx_chk_en:1; unsigned int tsb_en:1; unsigned int crc_fwd:1; u16 rev; -- cgit v1.2.3-59-g8ed1b From 83e82f4c706bbca3e2d9d7962e63605cc7a5fbd8 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 1 Jul 2014 21:08:40 -0700 Subject: net: systemport: add Wake-on-LAN support Support for Wake-on-LAN using Magic Packet with or without SecureOn password is implemented doing the following: - setting the password to the relevant UniMAC registers - flagging the device as a wakeup source for the system, as well as its Wake-on-LAN interrupt - prepare the hardware for entering WoL mode - enabling the MPD interrupt to wake us The Device Tree binding documentation is also reflected to specify the third optional Wake-on-LAN interrupt line. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- .../bindings/net/broadcom-systemport.txt | 3 +- drivers/net/ethernet/broadcom/bcmsysport.c | 155 ++++++++++++++++++++- drivers/net/ethernet/broadcom/bcmsysport.h | 12 ++ 3 files changed, 166 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/net/broadcom-systemport.txt b/Documentation/devicetree/bindings/net/broadcom-systemport.txt index c183ea90d9bc..aa7ad622259d 100644 --- a/Documentation/devicetree/bindings/net/broadcom-systemport.txt +++ b/Documentation/devicetree/bindings/net/broadcom-systemport.txt @@ -4,7 +4,8 @@ Required properties: - compatible: should be one of "brcm,systemport-v1.00" or "brcm,systemport" - reg: address and length of the register set for the device. - interrupts: interrupts for the device, first cell must be for the the rx - interrupts, and the second cell should be for the transmit queues + interrupts, and the second cell should be for the transmit queues. An + optional third interrupt cell for Wake-on-LAN can be specified - local-mac-address: Ethernet MAC address (48 bits) of this adapter - phy-mode: Should be a string describing the PHY interface to the Ethernet switch/PHY, see Documentation/devicetree/bindings/net/ethernet.txt diff --git a/drivers/net/ethernet/broadcom/bcmsysport.c b/drivers/net/ethernet/broadcom/bcmsysport.c index f00793e45330..7a1bd2b3bc26 100644 --- a/drivers/net/ethernet/broadcom/bcmsysport.c +++ b/drivers/net/ethernet/broadcom/bcmsysport.c @@ -384,6 +384,64 @@ static void bcm_sysport_get_stats(struct net_device *dev, } } +static void bcm_sysport_get_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct bcm_sysport_priv *priv = netdev_priv(dev); + u32 reg; + + wol->supported = WAKE_MAGIC | WAKE_MAGICSECURE; + wol->wolopts = priv->wolopts; + + if (!(priv->wolopts & WAKE_MAGICSECURE)) + return; + + /* Return the programmed SecureOn password */ + reg = umac_readl(priv, UMAC_PSW_MS); + put_unaligned_be16(reg, &wol->sopass[0]); + reg = umac_readl(priv, UMAC_PSW_LS); + put_unaligned_be32(reg, &wol->sopass[2]); +} + +static int bcm_sysport_set_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct bcm_sysport_priv *priv = netdev_priv(dev); + struct device *kdev = &priv->pdev->dev; + u32 supported = WAKE_MAGIC | WAKE_MAGICSECURE; + + if (!device_can_wakeup(kdev)) + return -ENOTSUPP; + + if (wol->wolopts & ~supported) + return -EINVAL; + + /* Program the SecureOn password */ + if (wol->wolopts & WAKE_MAGICSECURE) { + umac_writel(priv, get_unaligned_be16(&wol->sopass[0]), + UMAC_PSW_MS); + umac_writel(priv, get_unaligned_be32(&wol->sopass[2]), + UMAC_PSW_LS); + } + + /* Flag the device and relevant IRQ as wakeup capable */ + if (wol->wolopts) { + device_set_wakeup_enable(kdev, 1); + enable_irq_wake(priv->wol_irq); + priv->wol_irq_disabled = 0; + } else { + device_set_wakeup_enable(kdev, 0); + /* Avoid unbalanced disable_irq_wake calls */ + if (!priv->wol_irq_disabled) + disable_irq_wake(priv->wol_irq); + priv->wol_irq_disabled = 1; + } + + priv->wolopts = wol->wolopts; + + return 0; +} + static void bcm_sysport_free_cb(struct bcm_sysport_cb *cb) { dev_kfree_skb_any(cb->skb); @@ -692,6 +750,20 @@ static int bcm_sysport_poll(struct napi_struct *napi, int budget) return work_done; } +static void bcm_sysport_resume_from_wol(struct bcm_sysport_priv *priv) +{ + u32 reg; + + /* Stop monitoring MPD interrupt */ + intrl2_0_mask_set(priv, INTRL2_0_MPD); + + /* Clear the MagicPacket detection logic */ + reg = umac_readl(priv, UMAC_MPD_CTRL); + reg &= ~MPD_EN; + umac_writel(priv, reg, UMAC_MPD_CTRL); + + netif_dbg(priv, wol, priv->netdev, "resumed from WOL\n"); +} /* RX and misc interrupt routine */ static irqreturn_t bcm_sysport_rx_isr(int irq, void *dev_id) @@ -722,6 +794,11 @@ static irqreturn_t bcm_sysport_rx_isr(int irq, void *dev_id) if (priv->irq0_stat & INTRL2_0_TX_RING_FULL) bcm_sysport_tx_reclaim_all(priv); + if (priv->irq0_stat & INTRL2_0_MPD) { + netdev_info(priv->netdev, "Wake-on-LAN interrupt!\n"); + bcm_sysport_resume_from_wol(priv); + } + return IRQ_HANDLED; } @@ -757,6 +834,15 @@ static irqreturn_t bcm_sysport_tx_isr(int irq, void *dev_id) return IRQ_HANDLED; } +static irqreturn_t bcm_sysport_wol_isr(int irq, void *dev_id) +{ + struct bcm_sysport_priv *priv = dev_id; + + pm_wakeup_event(&priv->pdev->dev, 0); + + return IRQ_HANDLED; +} + static int bcm_sysport_insert_tsb(struct sk_buff *skb, struct net_device *dev) { struct sk_buff *nskb; @@ -1507,6 +1593,8 @@ static struct ethtool_ops bcm_sysport_ethtool_ops = { .get_strings = bcm_sysport_get_strings, .get_ethtool_stats = bcm_sysport_get_stats, .get_sset_count = bcm_sysport_get_sset_count, + .get_wol = bcm_sysport_get_wol, + .set_wol = bcm_sysport_set_wol, }; static const struct net_device_ops bcm_sysport_netdev_ops = { @@ -1548,6 +1636,7 @@ static int bcm_sysport_probe(struct platform_device *pdev) priv->irq0 = platform_get_irq(pdev, 0); priv->irq1 = platform_get_irq(pdev, 1); + priv->wol_irq = platform_get_irq(pdev, 2); if (priv->irq0 <= 0 || priv->irq1 <= 0) { dev_err(&pdev->dev, "invalid interrupts\n"); ret = -EINVAL; @@ -1600,6 +1689,13 @@ static int bcm_sysport_probe(struct platform_device *pdev) dev->hw_features |= NETIF_F_RXCSUM | NETIF_F_HIGHDMA | NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; + /* Request the WOL interrupt and advertise suspend if available */ + priv->wol_irq_disabled = 1; + ret = devm_request_irq(&pdev->dev, priv->wol_irq, + bcm_sysport_wol_isr, 0, dev->name, priv); + if (!ret) + device_set_wakeup_capable(&pdev->dev, 1); + /* Set the needed headroom once and for all */ BUILD_BUG_ON(sizeof(struct bcm_tsb) != 8); dev->needed_headroom += sizeof(struct bcm_tsb); @@ -1647,12 +1743,55 @@ static int bcm_sysport_remove(struct platform_device *pdev) } #ifdef CONFIG_PM_SLEEP +static int bcm_sysport_suspend_to_wol(struct bcm_sysport_priv *priv) +{ + struct net_device *ndev = priv->netdev; + unsigned int timeout = 1000; + u32 reg; + + /* Password has already been programmed */ + reg = umac_readl(priv, UMAC_MPD_CTRL); + reg |= MPD_EN; + reg &= ~PSW_EN; + if (priv->wolopts & WAKE_MAGICSECURE) + reg |= PSW_EN; + umac_writel(priv, reg, UMAC_MPD_CTRL); + + /* Make sure RBUF entered WoL mode as result */ + do { + reg = rbuf_readl(priv, RBUF_STATUS); + if (reg & RBUF_WOL_MODE) + break; + + udelay(10); + } while (timeout-- > 0); + + /* Do not leave the UniMAC RBUF matching only MPD packets */ + if (!timeout) { + reg = umac_readl(priv, UMAC_MPD_CTRL); + reg &= ~MPD_EN; + umac_writel(priv, reg, UMAC_MPD_CTRL); + netif_err(priv, wol, ndev, "failed to enter WOL mode\n"); + return -ETIMEDOUT; + } + + /* UniMAC receive needs to be turned on */ + umac_enable_set(priv, CMD_RX_EN, 1); + + /* Enable the interrupt wake-up source */ + intrl2_0_mask_clear(priv, INTRL2_0_MPD); + + netif_dbg(priv, wol, ndev, "entered WOL mode\n"); + + return 0; +} + static int bcm_sysport_suspend(struct device *d) { struct net_device *dev = dev_get_drvdata(d); struct bcm_sysport_priv *priv = netdev_priv(dev); unsigned int i; - int ret; + int ret = 0; u32 reg; if (!netif_running(dev)) @@ -1681,7 +1820,8 @@ static int bcm_sysport_suspend(struct device *d) } /* Flush RX pipe */ - topctrl_writel(priv, RX_FLUSH, RX_FLUSH_CNTL); + if (!priv->wolopts) + topctrl_writel(priv, RX_FLUSH, RX_FLUSH_CNTL); ret = tdma_enable_set(priv, 0); if (ret) { @@ -1701,7 +1841,11 @@ static int bcm_sysport_suspend(struct device *d) bcm_sysport_fini_tx_ring(priv, i); bcm_sysport_fini_rx_ring(priv); - return 0; + /* Get prepared for Wake-on-LAN */ + if (device_may_wakeup(d) && priv->wolopts) + ret = bcm_sysport_suspend_to_wol(priv); + + return ret; } static int bcm_sysport_resume(struct device *d) @@ -1715,6 +1859,11 @@ static int bcm_sysport_resume(struct device *d) if (!netif_running(dev)) return 0; + /* We may have been suspended and never received a WOL event that + * would turn off MPD detection, take care of that now + */ + bcm_sysport_resume_from_wol(priv); + /* Initialize both hardware and software ring */ for (i = 0; i < dev->num_tx_queues; i++) { ret = bcm_sysport_init_tx_ring(priv, i); diff --git a/drivers/net/ethernet/broadcom/bcmsysport.h b/drivers/net/ethernet/broadcom/bcmsysport.h index 20ea7162478f..b08dab828101 100644 --- a/drivers/net/ethernet/broadcom/bcmsysport.h +++ b/drivers/net/ethernet/broadcom/bcmsysport.h @@ -246,6 +246,15 @@ struct bcm_rsb { #define MIB_RX_CNT_RST (1 << 0) #define MIB_RUNT_CNT_RST (1 << 1) #define MIB_TX_CNT_RST (1 << 2) + +#define UMAC_MPD_CTRL 0x620 +#define MPD_EN (1 << 0) +#define MSEQ_LEN_SHIFT 16 +#define MSEQ_LEN_MASK 0xff +#define PSW_EN (1 << 27) + +#define UMAC_PSW_MS 0x624 +#define UMAC_PSW_LS 0x628 #define UMAC_MDF_CTRL 0x650 #define UMAC_MDF_ADDR 0x654 @@ -642,6 +651,7 @@ struct bcm_sysport_priv { struct platform_device *pdev; int irq0; int irq1; + int wol_irq; /* Transmit rings */ struct bcm_sysport_tx_ring tx_rings[TDMA_NUM_RINGS]; @@ -668,6 +678,8 @@ struct bcm_sysport_priv { unsigned int tsb_en:1; unsigned int crc_fwd:1; u16 rev; + u32 wolopts; + unsigned int wol_irq_disabled:1; /* MIB related fields */ struct bcm_sysport_mib mib; -- cgit v1.2.3-59-g8ed1b