diff options
author | David S. Miller <davem@davemloft.net> | 2014-07-07 20:56:55 -0700 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2014-07-07 20:56:55 -0700 |
commit | 081a20ffccd24d2f5fd06debcff57697fa2ff069 (patch) | |
tree | 5b594a1a1946c8c42609ae8ded841067bf94dd68 | |
parent | 4710d806fcb825156e0a7b3a81104915c5e90f5d (diff) | |
parent | 83e82f4c706bbca3e2d9d7962e63605cc7a5fbd8 (diff) | |
download | op-kernel-dev-081a20ffccd24d2f5fd06debcff57697fa2ff069.zip op-kernel-dev-081a20ffccd24d2f5fd06debcff57697fa2ff069.tar.gz |
Merge branch 'systemport-next'
Florian Fainelli says:
====================
net: systemport: PM and Wake-on-LAN support
This patchset brings Power Management and Wake-on-LAN support to the
Broadcom SYSTEM PORT driver.
S2 and S3 modes are supported, while we only support Wake-on-LAN using
MagicPackets for now
====================
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | Documentation/devicetree/bindings/net/broadcom-systemport.txt | 3 | ||||
-rw-r--r-- | drivers/net/ethernet/broadcom/bcmsysport.c | 378 | ||||
-rw-r--r-- | drivers/net/ethernet/broadcom/bcmsysport.h | 14 |
3 files changed, 361 insertions, 34 deletions
diff --git a/Documentation/devicetree/bindings/net/broadcom-systemport.txt b/Documentation/devicetree/bindings/net/broadcom-systemport.txt index c183ea9..aa7ad62 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 141160e..7a1bd2b 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; @@ -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; @@ -1236,15 +1322,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 @@ -1295,11 +1381,35 @@ 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 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 */ @@ -1313,12 +1423,10 @@ 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); - 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); @@ -1394,19 +1502,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, 1); - - phy_start(priv->phydev); + umac_enable_set(priv, CMD_RX_EN | CMD_TX_EN, 1); - /* 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,12 +1524,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; - u32 reg; - int ret; /* stop all software from updating hardware */ netif_tx_stop_all_queues(dev); @@ -1442,11 +1538,18 @@ 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 */ - 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 +1567,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++) @@ -1492,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 = { @@ -1533,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; @@ -1585,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); @@ -1631,6 +1742,208 @@ static int bcm_sysport_remove(struct platform_device *pdev) return 0; } +#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 = 0; + 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_chk_en) { + reg = rxchk_readl(priv, RXCHK_CONTROL); + reg &= ~RXCHK_EN; + rxchk_writel(priv, reg, RXCHK_CONTROL); + } + + /* Flush RX pipe */ + if (!priv->wolopts) + 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); + + /* 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) +{ + 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; + + /* 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); + 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_chk_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" }, @@ -1644,6 +1957,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); diff --git a/drivers/net/ethernet/broadcom/bcmsysport.h b/drivers/net/ethernet/broadcom/bcmsysport.h index 281c082..b08dab8 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]; @@ -664,10 +674,12 @@ 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; + u32 wolopts; + unsigned int wol_irq_disabled:1; /* MIB related fields */ struct bcm_sysport_mib mib; |