Commit 081a20ff authored by David S. Miller's avatar David S. Miller

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: default avatarDavid S. Miller <davem@davemloft.net>
parents 4710d806 83e82f4c
......@@ -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
......
......@@ -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 @@ static int bcm_sysport_open(struct net_device *dev)
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);
......
......@@ -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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment