Commit c3c9f441 authored by David S. Miller's avatar David S. Miller

Merge branch 'bcmgenet-start-stop-sequence-refinement'

Doug Berger says:

====================
net: bcmgenet: start/stop sequence refinement

This commit set is the result of an investigation into an issue that
occurred when bringing the interface up and down repeatedly with an
external 100BASE-T PHY. In some cases the MAC would experience mass
receive packet duplication that could in rare cases lead to a stall
from overflow.  The fix for this is contained in the third commit.

The first 3 commits represent bug fixes that should be applied to the
net repository and are candidates for backporting to stable releases.
The remaining commits are enhancements which is why the set is being
submitted to net-next but they are implemented on top of the fixes.

The first fix is provided as justification for why the set isn't
split between a net submission and a net-next submission.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 88ca59d1 6c97f010
...@@ -488,15 +488,13 @@ static void bcmgenet_complete(struct net_device *dev) ...@@ -488,15 +488,13 @@ static void bcmgenet_complete(struct net_device *dev)
static int bcmgenet_get_link_ksettings(struct net_device *dev, static int bcmgenet_get_link_ksettings(struct net_device *dev,
struct ethtool_link_ksettings *cmd) struct ethtool_link_ksettings *cmd)
{ {
struct bcmgenet_priv *priv = netdev_priv(dev);
if (!netif_running(dev)) if (!netif_running(dev))
return -EINVAL; return -EINVAL;
if (!priv->phydev) if (!dev->phydev)
return -ENODEV; return -ENODEV;
phy_ethtool_ksettings_get(priv->phydev, cmd); phy_ethtool_ksettings_get(dev->phydev, cmd);
return 0; return 0;
} }
...@@ -504,15 +502,13 @@ static int bcmgenet_get_link_ksettings(struct net_device *dev, ...@@ -504,15 +502,13 @@ static int bcmgenet_get_link_ksettings(struct net_device *dev,
static int bcmgenet_set_link_ksettings(struct net_device *dev, static int bcmgenet_set_link_ksettings(struct net_device *dev,
const struct ethtool_link_ksettings *cmd) const struct ethtool_link_ksettings *cmd)
{ {
struct bcmgenet_priv *priv = netdev_priv(dev);
if (!netif_running(dev)) if (!netif_running(dev))
return -EINVAL; return -EINVAL;
if (!priv->phydev) if (!dev->phydev)
return -ENODEV; return -ENODEV;
return phy_ethtool_ksettings_set(priv->phydev, cmd); return phy_ethtool_ksettings_set(dev->phydev, cmd);
} }
static int bcmgenet_set_rx_csum(struct net_device *dev, static int bcmgenet_set_rx_csum(struct net_device *dev,
...@@ -1042,11 +1038,14 @@ static int bcmgenet_get_eee(struct net_device *dev, struct ethtool_eee *e) ...@@ -1042,11 +1038,14 @@ static int bcmgenet_get_eee(struct net_device *dev, struct ethtool_eee *e)
if (GENET_IS_V1(priv)) if (GENET_IS_V1(priv))
return -EOPNOTSUPP; return -EOPNOTSUPP;
if (!dev->phydev)
return -ENODEV;
e->eee_enabled = p->eee_enabled; e->eee_enabled = p->eee_enabled;
e->eee_active = p->eee_active; e->eee_active = p->eee_active;
e->tx_lpi_timer = bcmgenet_umac_readl(priv, UMAC_EEE_LPI_TIMER); e->tx_lpi_timer = bcmgenet_umac_readl(priv, UMAC_EEE_LPI_TIMER);
return phy_ethtool_get_eee(priv->phydev, e); return phy_ethtool_get_eee(dev->phydev, e);
} }
static int bcmgenet_set_eee(struct net_device *dev, struct ethtool_eee *e) static int bcmgenet_set_eee(struct net_device *dev, struct ethtool_eee *e)
...@@ -1058,12 +1057,15 @@ static int bcmgenet_set_eee(struct net_device *dev, struct ethtool_eee *e) ...@@ -1058,12 +1057,15 @@ static int bcmgenet_set_eee(struct net_device *dev, struct ethtool_eee *e)
if (GENET_IS_V1(priv)) if (GENET_IS_V1(priv))
return -EOPNOTSUPP; return -EOPNOTSUPP;
if (!dev->phydev)
return -ENODEV;
p->eee_enabled = e->eee_enabled; p->eee_enabled = e->eee_enabled;
if (!p->eee_enabled) { if (!p->eee_enabled) {
bcmgenet_eee_enable_set(dev, false); bcmgenet_eee_enable_set(dev, false);
} else { } else {
ret = phy_init_eee(priv->phydev, 0); ret = phy_init_eee(dev->phydev, 0);
if (ret) { if (ret) {
netif_err(priv, hw, dev, "EEE initialization failed\n"); netif_err(priv, hw, dev, "EEE initialization failed\n");
return ret; return ret;
...@@ -1073,7 +1075,7 @@ static int bcmgenet_set_eee(struct net_device *dev, struct ethtool_eee *e) ...@@ -1073,7 +1075,7 @@ static int bcmgenet_set_eee(struct net_device *dev, struct ethtool_eee *e)
bcmgenet_eee_enable_set(dev, true); bcmgenet_eee_enable_set(dev, true);
} }
return phy_ethtool_set_eee(priv->phydev, e); return phy_ethtool_set_eee(dev->phydev, e);
} }
/* standard ethtool support functions. */ /* standard ethtool support functions. */
...@@ -1107,7 +1109,7 @@ static int bcmgenet_power_down(struct bcmgenet_priv *priv, ...@@ -1107,7 +1109,7 @@ static int bcmgenet_power_down(struct bcmgenet_priv *priv,
switch (mode) { switch (mode) {
case GENET_POWER_CABLE_SENSE: case GENET_POWER_CABLE_SENSE:
phy_detach(priv->phydev); phy_detach(priv->dev->phydev);
break; break;
case GENET_POWER_WOL_MAGIC: case GENET_POWER_WOL_MAGIC:
...@@ -1172,7 +1174,6 @@ static void bcmgenet_power_up(struct bcmgenet_priv *priv, ...@@ -1172,7 +1174,6 @@ static void bcmgenet_power_up(struct bcmgenet_priv *priv,
} }
bcmgenet_ext_writel(priv, reg, EXT_EXT_PWR_MGMT); bcmgenet_ext_writel(priv, reg, EXT_EXT_PWR_MGMT);
bcmgenet_phy_power_set(priv->dev, true); bcmgenet_phy_power_set(priv->dev, true);
bcmgenet_mii_reset(priv->dev);
break; break;
case GENET_POWER_CABLE_SENSE: case GENET_POWER_CABLE_SENSE:
...@@ -1193,15 +1194,13 @@ static void bcmgenet_power_up(struct bcmgenet_priv *priv, ...@@ -1193,15 +1194,13 @@ static void bcmgenet_power_up(struct bcmgenet_priv *priv,
/* ioctl handle special commands that are not present in ethtool. */ /* ioctl handle special commands that are not present in ethtool. */
static int bcmgenet_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) static int bcmgenet_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{ {
struct bcmgenet_priv *priv = netdev_priv(dev);
if (!netif_running(dev)) if (!netif_running(dev))
return -EINVAL; return -EINVAL;
if (!priv->phydev) if (!dev->phydev)
return -ENODEV; return -ENODEV;
return phy_mii_ioctl(priv->phydev, rq, cmd); return phy_mii_ioctl(dev->phydev, rq, cmd);
} }
static struct enet_cb *bcmgenet_get_txcb(struct bcmgenet_priv *priv, static struct enet_cb *bcmgenet_get_txcb(struct bcmgenet_priv *priv,
...@@ -1405,11 +1404,10 @@ static unsigned int bcmgenet_tx_reclaim(struct net_device *dev, ...@@ -1405,11 +1404,10 @@ static unsigned int bcmgenet_tx_reclaim(struct net_device *dev,
struct bcmgenet_tx_ring *ring) struct bcmgenet_tx_ring *ring)
{ {
unsigned int released; unsigned int released;
unsigned long flags;
spin_lock_irqsave(&ring->lock, flags); spin_lock_bh(&ring->lock);
released = __bcmgenet_tx_reclaim(dev, ring); released = __bcmgenet_tx_reclaim(dev, ring);
spin_unlock_irqrestore(&ring->lock, flags); spin_unlock_bh(&ring->lock);
return released; return released;
} }
...@@ -1420,15 +1418,14 @@ static int bcmgenet_tx_poll(struct napi_struct *napi, int budget) ...@@ -1420,15 +1418,14 @@ static int bcmgenet_tx_poll(struct napi_struct *napi, int budget)
container_of(napi, struct bcmgenet_tx_ring, napi); container_of(napi, struct bcmgenet_tx_ring, napi);
unsigned int work_done = 0; unsigned int work_done = 0;
struct netdev_queue *txq; struct netdev_queue *txq;
unsigned long flags;
spin_lock_irqsave(&ring->lock, flags); spin_lock(&ring->lock);
work_done = __bcmgenet_tx_reclaim(ring->priv->dev, ring); work_done = __bcmgenet_tx_reclaim(ring->priv->dev, ring);
if (ring->free_bds > (MAX_SKB_FRAGS + 1)) { if (ring->free_bds > (MAX_SKB_FRAGS + 1)) {
txq = netdev_get_tx_queue(ring->priv->dev, ring->queue); txq = netdev_get_tx_queue(ring->priv->dev, ring->queue);
netif_tx_wake_queue(txq); netif_tx_wake_queue(txq);
} }
spin_unlock_irqrestore(&ring->lock, flags); spin_unlock(&ring->lock);
if (work_done == 0) { if (work_done == 0) {
napi_complete(napi); napi_complete(napi);
...@@ -1523,7 +1520,6 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev) ...@@ -1523,7 +1520,6 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
struct bcmgenet_tx_ring *ring = NULL; struct bcmgenet_tx_ring *ring = NULL;
struct enet_cb *tx_cb_ptr; struct enet_cb *tx_cb_ptr;
struct netdev_queue *txq; struct netdev_queue *txq;
unsigned long flags = 0;
int nr_frags, index; int nr_frags, index;
dma_addr_t mapping; dma_addr_t mapping;
unsigned int size; unsigned int size;
...@@ -1550,7 +1546,7 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev) ...@@ -1550,7 +1546,7 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
nr_frags = skb_shinfo(skb)->nr_frags; nr_frags = skb_shinfo(skb)->nr_frags;
spin_lock_irqsave(&ring->lock, flags); spin_lock(&ring->lock);
if (ring->free_bds <= (nr_frags + 1)) { if (ring->free_bds <= (nr_frags + 1)) {
if (!netif_tx_queue_stopped(txq)) { if (!netif_tx_queue_stopped(txq)) {
netif_tx_stop_queue(txq); netif_tx_stop_queue(txq);
...@@ -1645,7 +1641,7 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev) ...@@ -1645,7 +1641,7 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
bcmgenet_tdma_ring_writel(priv, ring->index, bcmgenet_tdma_ring_writel(priv, ring->index,
ring->prod_index, TDMA_PROD_INDEX); ring->prod_index, TDMA_PROD_INDEX);
out: out:
spin_unlock_irqrestore(&ring->lock, flags); spin_unlock(&ring->lock);
return ret; return ret;
...@@ -1935,12 +1931,8 @@ static void umac_enable_set(struct bcmgenet_priv *priv, u32 mask, bool enable) ...@@ -1935,12 +1931,8 @@ static void umac_enable_set(struct bcmgenet_priv *priv, u32 mask, bool enable)
usleep_range(1000, 2000); usleep_range(1000, 2000);
} }
static int reset_umac(struct bcmgenet_priv *priv) static void reset_umac(struct bcmgenet_priv *priv)
{ {
struct device *kdev = &priv->pdev->dev;
unsigned int timeout = 0;
u32 reg;
/* 7358a0/7552a0: bad default in RBUF_FLUSH_CTRL.umac_sw_rst */ /* 7358a0/7552a0: bad default in RBUF_FLUSH_CTRL.umac_sw_rst */
bcmgenet_rbuf_ctrl_set(priv, 0); bcmgenet_rbuf_ctrl_set(priv, 0);
udelay(10); udelay(10);
...@@ -1948,23 +1940,10 @@ static int reset_umac(struct bcmgenet_priv *priv) ...@@ -1948,23 +1940,10 @@ static int reset_umac(struct bcmgenet_priv *priv)
/* disable MAC while updating its registers */ /* disable MAC while updating its registers */
bcmgenet_umac_writel(priv, 0, UMAC_CMD); bcmgenet_umac_writel(priv, 0, UMAC_CMD);
/* issue soft reset, wait for it to complete */ /* issue soft reset with (rg)mii loopback to ensure a stable rxclk */
bcmgenet_umac_writel(priv, CMD_SW_RESET, UMAC_CMD); bcmgenet_umac_writel(priv, CMD_SW_RESET | CMD_LCL_LOOP_EN, UMAC_CMD);
while (timeout++ < 1000) { udelay(2);
reg = bcmgenet_umac_readl(priv, UMAC_CMD); bcmgenet_umac_writel(priv, 0, UMAC_CMD);
if (!(reg & CMD_SW_RESET))
return 0;
udelay(1);
}
if (timeout == 1000) {
dev_err(kdev,
"timeout waiting for MAC to come out of reset\n");
return -ETIMEDOUT;
}
return 0;
} }
static void bcmgenet_intr_disable(struct bcmgenet_priv *priv) static void bcmgenet_intr_disable(struct bcmgenet_priv *priv)
...@@ -1994,20 +1973,16 @@ static void bcmgenet_link_intr_enable(struct bcmgenet_priv *priv) ...@@ -1994,20 +1973,16 @@ static void bcmgenet_link_intr_enable(struct bcmgenet_priv *priv)
bcmgenet_intrl2_0_writel(priv, int0_enable, INTRL2_CPU_MASK_CLEAR); bcmgenet_intrl2_0_writel(priv, int0_enable, INTRL2_CPU_MASK_CLEAR);
} }
static int init_umac(struct bcmgenet_priv *priv) static void init_umac(struct bcmgenet_priv *priv)
{ {
struct device *kdev = &priv->pdev->dev; struct device *kdev = &priv->pdev->dev;
int ret;
u32 reg; u32 reg;
u32 int0_enable = 0; u32 int0_enable = 0;
dev_dbg(&priv->pdev->dev, "bcmgenet: init_umac\n"); dev_dbg(&priv->pdev->dev, "bcmgenet: init_umac\n");
ret = reset_umac(priv); reset_umac(priv);
if (ret)
return ret;
bcmgenet_umac_writel(priv, 0, UMAC_CMD);
/* clear tx/rx counter */ /* clear tx/rx counter */
bcmgenet_umac_writel(priv, bcmgenet_umac_writel(priv,
MIB_RESET_RX | MIB_RESET_TX | MIB_RESET_RUNT, MIB_RESET_RX | MIB_RESET_TX | MIB_RESET_RUNT,
...@@ -2046,8 +2021,6 @@ static int init_umac(struct bcmgenet_priv *priv) ...@@ -2046,8 +2021,6 @@ static int init_umac(struct bcmgenet_priv *priv)
bcmgenet_intrl2_0_writel(priv, int0_enable, INTRL2_CPU_MASK_CLEAR); bcmgenet_intrl2_0_writel(priv, int0_enable, INTRL2_CPU_MASK_CLEAR);
dev_dbg(kdev, "done init umac\n"); dev_dbg(kdev, "done init umac\n");
return 0;
} }
/* Initialize a Tx ring along with corresponding hardware registers */ /* Initialize a Tx ring along with corresponding hardware registers */
...@@ -2104,6 +2077,10 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv, ...@@ -2104,6 +2077,10 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv,
TDMA_WRITE_PTR); TDMA_WRITE_PTR);
bcmgenet_tdma_ring_writel(priv, index, end_ptr * words_per_bd - 1, bcmgenet_tdma_ring_writel(priv, index, end_ptr * words_per_bd - 1,
DMA_END_ADDR); DMA_END_ADDR);
/* Initialize Tx NAPI */
netif_napi_add(priv->dev, &ring->napi, bcmgenet_tx_poll,
NAPI_POLL_WEIGHT);
} }
/* Initialize a RDMA ring */ /* Initialize a RDMA ring */
...@@ -2135,6 +2112,10 @@ static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv, ...@@ -2135,6 +2112,10 @@ static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv,
if (ret) if (ret)
return ret; return ret;
/* Initialize Rx NAPI */
netif_napi_add(priv->dev, &ring->napi, bcmgenet_rx_poll,
NAPI_POLL_WEIGHT);
bcmgenet_rdma_ring_writel(priv, index, 0, RDMA_PROD_INDEX); bcmgenet_rdma_ring_writel(priv, index, 0, RDMA_PROD_INDEX);
bcmgenet_rdma_ring_writel(priv, index, 0, RDMA_CONS_INDEX); bcmgenet_rdma_ring_writel(priv, index, 0, RDMA_CONS_INDEX);
bcmgenet_rdma_ring_writel(priv, index, 1, DMA_MBUF_DONE_THRESH); bcmgenet_rdma_ring_writel(priv, index, 1, DMA_MBUF_DONE_THRESH);
...@@ -2159,50 +2140,27 @@ static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv, ...@@ -2159,50 +2140,27 @@ static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv,
return ret; return ret;
} }
static void bcmgenet_init_tx_napi(struct bcmgenet_priv *priv)
{
unsigned int i;
struct bcmgenet_tx_ring *ring;
for (i = 0; i < priv->hw_params->tx_queues; ++i) {
ring = &priv->tx_rings[i];
netif_tx_napi_add(priv->dev, &ring->napi, bcmgenet_tx_poll, 64);
}
ring = &priv->tx_rings[DESC_INDEX];
netif_tx_napi_add(priv->dev, &ring->napi, bcmgenet_tx_poll, 64);
}
static void bcmgenet_enable_tx_napi(struct bcmgenet_priv *priv) static void bcmgenet_enable_tx_napi(struct bcmgenet_priv *priv)
{ {
unsigned int i; unsigned int i;
u32 int0_enable = UMAC_IRQ_TXDMA_DONE;
u32 int1_enable = 0;
struct bcmgenet_tx_ring *ring; struct bcmgenet_tx_ring *ring;
for (i = 0; i < priv->hw_params->tx_queues; ++i) { for (i = 0; i < priv->hw_params->tx_queues; ++i) {
ring = &priv->tx_rings[i]; ring = &priv->tx_rings[i];
napi_enable(&ring->napi); napi_enable(&ring->napi);
int1_enable |= (1 << i); ring->int_enable(ring);
} }
ring = &priv->tx_rings[DESC_INDEX]; ring = &priv->tx_rings[DESC_INDEX];
napi_enable(&ring->napi); napi_enable(&ring->napi);
ring->int_enable(ring);
bcmgenet_intrl2_0_writel(priv, int0_enable, INTRL2_CPU_MASK_CLEAR);
bcmgenet_intrl2_1_writel(priv, int1_enable, INTRL2_CPU_MASK_CLEAR);
} }
static void bcmgenet_disable_tx_napi(struct bcmgenet_priv *priv) static void bcmgenet_disable_tx_napi(struct bcmgenet_priv *priv)
{ {
unsigned int i; unsigned int i;
u32 int0_disable = UMAC_IRQ_TXDMA_DONE;
u32 int1_disable = 0xffff;
struct bcmgenet_tx_ring *ring; struct bcmgenet_tx_ring *ring;
bcmgenet_intrl2_0_writel(priv, int0_disable, INTRL2_CPU_MASK_SET);
bcmgenet_intrl2_1_writel(priv, int1_disable, INTRL2_CPU_MASK_SET);
for (i = 0; i < priv->hw_params->tx_queues; ++i) { for (i = 0; i < priv->hw_params->tx_queues; ++i) {
ring = &priv->tx_rings[i]; ring = &priv->tx_rings[i];
napi_disable(&ring->napi); napi_disable(&ring->napi);
...@@ -2286,9 +2244,6 @@ static void bcmgenet_init_tx_queues(struct net_device *dev) ...@@ -2286,9 +2244,6 @@ static void bcmgenet_init_tx_queues(struct net_device *dev)
bcmgenet_tdma_writel(priv, dma_priority[1], DMA_PRIORITY_1); bcmgenet_tdma_writel(priv, dma_priority[1], DMA_PRIORITY_1);
bcmgenet_tdma_writel(priv, dma_priority[2], DMA_PRIORITY_2); bcmgenet_tdma_writel(priv, dma_priority[2], DMA_PRIORITY_2);
/* Initialize Tx NAPI */
bcmgenet_init_tx_napi(priv);
/* Enable Tx queues */ /* Enable Tx queues */
bcmgenet_tdma_writel(priv, ring_cfg, DMA_RING_CFG); bcmgenet_tdma_writel(priv, ring_cfg, DMA_RING_CFG);
...@@ -2298,50 +2253,27 @@ static void bcmgenet_init_tx_queues(struct net_device *dev) ...@@ -2298,50 +2253,27 @@ static void bcmgenet_init_tx_queues(struct net_device *dev)
bcmgenet_tdma_writel(priv, dma_ctrl, DMA_CTRL); bcmgenet_tdma_writel(priv, dma_ctrl, DMA_CTRL);
} }
static void bcmgenet_init_rx_napi(struct bcmgenet_priv *priv)
{
unsigned int i;
struct bcmgenet_rx_ring *ring;
for (i = 0; i < priv->hw_params->rx_queues; ++i) {
ring = &priv->rx_rings[i];
netif_napi_add(priv->dev, &ring->napi, bcmgenet_rx_poll, 64);
}
ring = &priv->rx_rings[DESC_INDEX];
netif_napi_add(priv->dev, &ring->napi, bcmgenet_rx_poll, 64);
}
static void bcmgenet_enable_rx_napi(struct bcmgenet_priv *priv) static void bcmgenet_enable_rx_napi(struct bcmgenet_priv *priv)
{ {
unsigned int i; unsigned int i;
u32 int0_enable = UMAC_IRQ_RXDMA_DONE;
u32 int1_enable = 0;
struct bcmgenet_rx_ring *ring; struct bcmgenet_rx_ring *ring;
for (i = 0; i < priv->hw_params->rx_queues; ++i) { for (i = 0; i < priv->hw_params->rx_queues; ++i) {
ring = &priv->rx_rings[i]; ring = &priv->rx_rings[i];
napi_enable(&ring->napi); napi_enable(&ring->napi);
int1_enable |= (1 << (UMAC_IRQ1_RX_INTR_SHIFT + i)); ring->int_enable(ring);
} }
ring = &priv->rx_rings[DESC_INDEX]; ring = &priv->rx_rings[DESC_INDEX];
napi_enable(&ring->napi); napi_enable(&ring->napi);
ring->int_enable(ring);
bcmgenet_intrl2_0_writel(priv, int0_enable, INTRL2_CPU_MASK_CLEAR);
bcmgenet_intrl2_1_writel(priv, int1_enable, INTRL2_CPU_MASK_CLEAR);
} }
static void bcmgenet_disable_rx_napi(struct bcmgenet_priv *priv) static void bcmgenet_disable_rx_napi(struct bcmgenet_priv *priv)
{ {
unsigned int i; unsigned int i;
u32 int0_disable = UMAC_IRQ_RXDMA_DONE;
u32 int1_disable = 0xffff << UMAC_IRQ1_RX_INTR_SHIFT;
struct bcmgenet_rx_ring *ring; struct bcmgenet_rx_ring *ring;
bcmgenet_intrl2_0_writel(priv, int0_disable, INTRL2_CPU_MASK_SET);
bcmgenet_intrl2_1_writel(priv, int1_disable, INTRL2_CPU_MASK_SET);
for (i = 0; i < priv->hw_params->rx_queues; ++i) { for (i = 0; i < priv->hw_params->rx_queues; ++i) {
ring = &priv->rx_rings[i]; ring = &priv->rx_rings[i];
napi_disable(&ring->napi); napi_disable(&ring->napi);
...@@ -2414,9 +2346,6 @@ static int bcmgenet_init_rx_queues(struct net_device *dev) ...@@ -2414,9 +2346,6 @@ static int bcmgenet_init_rx_queues(struct net_device *dev)
ring_cfg |= (1 << DESC_INDEX); ring_cfg |= (1 << DESC_INDEX);
dma_ctrl |= (1 << (DESC_INDEX + DMA_RING_BUF_EN_SHIFT)); dma_ctrl |= (1 << (DESC_INDEX + DMA_RING_BUF_EN_SHIFT));
/* Initialize Rx NAPI */
bcmgenet_init_rx_napi(priv);
/* Enable rings */ /* Enable rings */
bcmgenet_rdma_writel(priv, ring_cfg, DMA_RING_CFG); bcmgenet_rdma_writel(priv, ring_cfg, DMA_RING_CFG);
...@@ -2505,9 +2434,6 @@ static void bcmgenet_fini_dma(struct bcmgenet_priv *priv) ...@@ -2505,9 +2434,6 @@ static void bcmgenet_fini_dma(struct bcmgenet_priv *priv)
bcmgenet_fini_rx_napi(priv); bcmgenet_fini_rx_napi(priv);
bcmgenet_fini_tx_napi(priv); bcmgenet_fini_tx_napi(priv);
/* disable DMA */
bcmgenet_dma_teardown(priv);
for (i = 0; i < priv->num_tx_bds; i++) { for (i = 0; i < priv->num_tx_bds; i++) {
cb = priv->tx_cbs + i; cb = priv->tx_cbs + i;
skb = bcmgenet_free_tx_cb(&priv->pdev->dev, cb); skb = bcmgenet_free_tx_cb(&priv->pdev->dev, cb);
...@@ -2590,27 +2516,20 @@ static int bcmgenet_init_dma(struct bcmgenet_priv *priv) ...@@ -2590,27 +2516,20 @@ static int bcmgenet_init_dma(struct bcmgenet_priv *priv)
/* Interrupt bottom half */ /* Interrupt bottom half */
static void bcmgenet_irq_task(struct work_struct *work) static void bcmgenet_irq_task(struct work_struct *work)
{ {
unsigned long flags;
unsigned int status; unsigned int status;
struct bcmgenet_priv *priv = container_of( struct bcmgenet_priv *priv = container_of(
work, struct bcmgenet_priv, bcmgenet_irq_work); work, struct bcmgenet_priv, bcmgenet_irq_work);
netif_dbg(priv, intr, priv->dev, "%s\n", __func__); netif_dbg(priv, intr, priv->dev, "%s\n", __func__);
spin_lock_irqsave(&priv->lock, flags); spin_lock_irq(&priv->lock);
status = priv->irq0_stat; status = priv->irq0_stat;
priv->irq0_stat = 0; priv->irq0_stat = 0;
spin_unlock_irqrestore(&priv->lock, flags); spin_unlock_irq(&priv->lock);
if (status & UMAC_IRQ_MPD_R) {
netif_dbg(priv, wol, priv->dev,
"magic packet detected, waking up\n");
bcmgenet_power_up(priv, GENET_POWER_WOL_MAGIC);
}
/* Link UP/DOWN event */ /* Link UP/DOWN event */
if (status & UMAC_IRQ_LINK_EVENT) if (status & UMAC_IRQ_LINK_EVENT)
phy_mac_interrupt(priv->phydev, phy_mac_interrupt(priv->dev->phydev,
!!(status & UMAC_IRQ_LINK_UP)); !!(status & UMAC_IRQ_LINK_UP));
} }
...@@ -2698,23 +2617,13 @@ static irqreturn_t bcmgenet_isr0(int irq, void *dev_id) ...@@ -2698,23 +2617,13 @@ static irqreturn_t bcmgenet_isr0(int irq, void *dev_id)
} }
} }
if (priv->irq0_stat & (UMAC_IRQ_PHY_DET_R |
UMAC_IRQ_PHY_DET_F |
UMAC_IRQ_LINK_EVENT |
UMAC_IRQ_HFB_SM |
UMAC_IRQ_HFB_MM)) {
/* all other interested interrupts handled in bottom half */
schedule_work(&priv->bcmgenet_irq_work);
}
if ((priv->hw_params->flags & GENET_HAS_MDIO_INTR) && if ((priv->hw_params->flags & GENET_HAS_MDIO_INTR) &&
status & (UMAC_IRQ_MDIO_DONE | UMAC_IRQ_MDIO_ERROR)) { status & (UMAC_IRQ_MDIO_DONE | UMAC_IRQ_MDIO_ERROR)) {
wake_up(&priv->wq); wake_up(&priv->wq);
} }
/* all other interested interrupts handled in bottom half */ /* all other interested interrupts handled in bottom half */
status &= (UMAC_IRQ_LINK_EVENT | status &= UMAC_IRQ_LINK_EVENT;
UMAC_IRQ_MPD_R);
if (status) { if (status) {
/* Save irq status for bottom-half processing. */ /* Save irq status for bottom-half processing. */
spin_lock_irqsave(&priv->lock, flags); spin_lock_irqsave(&priv->lock, flags);
...@@ -2849,16 +2758,16 @@ static void bcmgenet_netif_start(struct net_device *dev) ...@@ -2849,16 +2758,16 @@ static void bcmgenet_netif_start(struct net_device *dev)
/* Start the network engine */ /* Start the network engine */
bcmgenet_enable_rx_napi(priv); bcmgenet_enable_rx_napi(priv);
bcmgenet_enable_tx_napi(priv);
umac_enable_set(priv, CMD_TX_EN | CMD_RX_EN, true); umac_enable_set(priv, CMD_TX_EN | CMD_RX_EN, true);
netif_tx_start_all_queues(dev); netif_tx_start_all_queues(dev);
bcmgenet_enable_tx_napi(priv);
/* Monitor link interrupts now */ /* Monitor link interrupts now */
bcmgenet_link_intr_enable(priv); bcmgenet_link_intr_enable(priv);
phy_start(priv->phydev); phy_start(dev->phydev);
} }
static int bcmgenet_open(struct net_device *dev) static int bcmgenet_open(struct net_device *dev)
...@@ -2882,12 +2791,7 @@ static int bcmgenet_open(struct net_device *dev) ...@@ -2882,12 +2791,7 @@ static int bcmgenet_open(struct net_device *dev)
/* take MAC out of reset */ /* take MAC out of reset */
bcmgenet_umac_reset(priv); bcmgenet_umac_reset(priv);
ret = init_umac(priv); init_umac(priv);
if (ret)
goto err_clk_disable;
/* disable ethernet MAC while updating its registers */
umac_enable_set(priv, CMD_TX_EN | CMD_RX_EN, false);
/* Make sure we reflect the value of CRC_CMD_FWD */ /* Make sure we reflect the value of CRC_CMD_FWD */
reg = bcmgenet_umac_readl(priv, UMAC_CMD); reg = bcmgenet_umac_readl(priv, UMAC_CMD);
...@@ -2946,6 +2850,7 @@ static int bcmgenet_open(struct net_device *dev) ...@@ -2946,6 +2850,7 @@ static int bcmgenet_open(struct net_device *dev)
err_irq0: err_irq0:
free_irq(priv->irq0, priv); free_irq(priv->irq0, priv);
err_fini_dma: err_fini_dma:
bcmgenet_dma_teardown(priv);
bcmgenet_fini_dma(priv); bcmgenet_fini_dma(priv);
err_clk_disable: err_clk_disable:
if (priv->internal_phy) if (priv->internal_phy)
...@@ -2958,11 +2863,20 @@ static void bcmgenet_netif_stop(struct net_device *dev) ...@@ -2958,11 +2863,20 @@ static void bcmgenet_netif_stop(struct net_device *dev)
{ {
struct bcmgenet_priv *priv = netdev_priv(dev); struct bcmgenet_priv *priv = netdev_priv(dev);
bcmgenet_disable_tx_napi(priv);
netif_tx_stop_all_queues(dev); netif_tx_stop_all_queues(dev);
phy_stop(priv->phydev);
bcmgenet_intr_disable(priv); /* Disable MAC receive */
umac_enable_set(priv, CMD_RX_EN, false);
bcmgenet_dma_teardown(priv);
/* Disable MAC transmit. TX DMA disabled must be done before this */
umac_enable_set(priv, CMD_TX_EN, false);
phy_stop(dev->phydev);
bcmgenet_disable_rx_napi(priv); bcmgenet_disable_rx_napi(priv);
bcmgenet_disable_tx_napi(priv); bcmgenet_intr_disable(priv);
/* Wait for pending work items to complete. Since interrupts are /* Wait for pending work items to complete. Since interrupts are
* disabled no new work will be scheduled. * disabled no new work will be scheduled.
...@@ -2973,33 +2887,23 @@ static void bcmgenet_netif_stop(struct net_device *dev) ...@@ -2973,33 +2887,23 @@ static void bcmgenet_netif_stop(struct net_device *dev)
priv->old_speed = -1; priv->old_speed = -1;
priv->old_duplex = -1; priv->old_duplex = -1;
priv->old_pause = -1; priv->old_pause = -1;
/* tx reclaim */
bcmgenet_tx_reclaim_all(dev);
bcmgenet_fini_dma(priv);
} }
static int bcmgenet_close(struct net_device *dev) static int bcmgenet_close(struct net_device *dev)
{ {
struct bcmgenet_priv *priv = netdev_priv(dev); struct bcmgenet_priv *priv = netdev_priv(dev);
int ret; int ret = 0;
netif_dbg(priv, ifdown, dev, "bcmgenet_close\n"); netif_dbg(priv, ifdown, dev, "bcmgenet_close\n");
bcmgenet_netif_stop(dev); bcmgenet_netif_stop(dev);
/* Really kill the PHY state machine and disconnect from it */ /* Really kill the PHY state machine and disconnect from it */
phy_disconnect(priv->phydev); phy_disconnect(dev->phydev);
/* Disable MAC receive */
umac_enable_set(priv, CMD_RX_EN, false);
ret = bcmgenet_dma_teardown(priv);
if (ret)
return ret;
/* Disable MAC transmit. TX DMA disabled must be done before this */
umac_enable_set(priv, CMD_TX_EN, false);
/* tx reclaim */
bcmgenet_tx_reclaim_all(dev);
bcmgenet_fini_dma(priv);
free_irq(priv->irq0, priv); free_irq(priv->irq0, priv);
free_irq(priv->irq1, priv); free_irq(priv->irq1, priv);
...@@ -3018,7 +2922,6 @@ static void bcmgenet_dump_tx_queue(struct bcmgenet_tx_ring *ring) ...@@ -3018,7 +2922,6 @@ static void bcmgenet_dump_tx_queue(struct bcmgenet_tx_ring *ring)
u32 p_index, c_index, intsts, intmsk; u32 p_index, c_index, intsts, intmsk;
struct netdev_queue *txq; struct netdev_queue *txq;
unsigned int free_bds; unsigned int free_bds;
unsigned long flags;
bool txq_stopped; bool txq_stopped;
if (!netif_msg_tx_err(priv)) if (!netif_msg_tx_err(priv))
...@@ -3026,7 +2929,7 @@ static void bcmgenet_dump_tx_queue(struct bcmgenet_tx_ring *ring) ...@@ -3026,7 +2929,7 @@ static void bcmgenet_dump_tx_queue(struct bcmgenet_tx_ring *ring)
txq = netdev_get_tx_queue(priv->dev, ring->queue); txq = netdev_get_tx_queue(priv->dev, ring->queue);
spin_lock_irqsave(&ring->lock, flags); spin_lock(&ring->lock);
if (ring->index == DESC_INDEX) { if (ring->index == DESC_INDEX) {
intsts = ~bcmgenet_intrl2_0_readl(priv, INTRL2_CPU_MASK_STATUS); intsts = ~bcmgenet_intrl2_0_readl(priv, INTRL2_CPU_MASK_STATUS);
intmsk = UMAC_IRQ_TXDMA_DONE | UMAC_IRQ_TXDMA_MBDONE; intmsk = UMAC_IRQ_TXDMA_DONE | UMAC_IRQ_TXDMA_MBDONE;
...@@ -3038,7 +2941,7 @@ static void bcmgenet_dump_tx_queue(struct bcmgenet_tx_ring *ring) ...@@ -3038,7 +2941,7 @@ static void bcmgenet_dump_tx_queue(struct bcmgenet_tx_ring *ring)
p_index = bcmgenet_tdma_ring_readl(priv, ring->index, TDMA_PROD_INDEX); p_index = bcmgenet_tdma_ring_readl(priv, ring->index, TDMA_PROD_INDEX);
txq_stopped = netif_tx_queue_stopped(txq); txq_stopped = netif_tx_queue_stopped(txq);
free_bds = ring->free_bds; free_bds = ring->free_bds;
spin_unlock_irqrestore(&ring->lock, flags); spin_unlock(&ring->lock);
netif_err(priv, tx_err, priv->dev, "Ring %d queue %d status summary\n" netif_err(priv, tx_err, priv->dev, "Ring %d queue %d status summary\n"
"TX queue status: %s, interrupts: %s\n" "TX queue status: %s, interrupts: %s\n"
...@@ -3564,9 +3467,7 @@ static int bcmgenet_probe(struct platform_device *pdev) ...@@ -3564,9 +3467,7 @@ static int bcmgenet_probe(struct platform_device *pdev)
!strcasecmp(phy_mode_str, "internal")) !strcasecmp(phy_mode_str, "internal"))
bcmgenet_power_up(priv, GENET_POWER_PASSIVE); bcmgenet_power_up(priv, GENET_POWER_PASSIVE);
err = reset_umac(priv); reset_umac(priv);
if (err)
goto err_clk_disable;
err = bcmgenet_mii_init(dev); err = bcmgenet_mii_init(dev);
if (err) if (err)
...@@ -3614,7 +3515,7 @@ static int bcmgenet_suspend(struct device *d) ...@@ -3614,7 +3515,7 @@ static int bcmgenet_suspend(struct device *d)
{ {
struct net_device *dev = dev_get_drvdata(d); struct net_device *dev = dev_get_drvdata(d);
struct bcmgenet_priv *priv = netdev_priv(dev); struct bcmgenet_priv *priv = netdev_priv(dev);
int ret; int ret = 0;
if (!netif_running(dev)) if (!netif_running(dev))
return 0; return 0;
...@@ -3622,24 +3523,10 @@ static int bcmgenet_suspend(struct device *d) ...@@ -3622,24 +3523,10 @@ static int bcmgenet_suspend(struct device *d)
bcmgenet_netif_stop(dev); bcmgenet_netif_stop(dev);
if (!device_may_wakeup(d)) if (!device_may_wakeup(d))
phy_suspend(priv->phydev); phy_suspend(dev->phydev);
netif_device_detach(dev); netif_device_detach(dev);
/* Disable MAC receive */
umac_enable_set(priv, CMD_RX_EN, false);
ret = bcmgenet_dma_teardown(priv);
if (ret)
return ret;
/* Disable MAC transmit. TX DMA disabled must be done before this */
umac_enable_set(priv, CMD_TX_EN, false);
/* tx reclaim */
bcmgenet_tx_reclaim_all(dev);
bcmgenet_fini_dma(priv);
/* Prepare the device for Wake-on-LAN and switch to the slow clock */ /* Prepare the device for Wake-on-LAN and switch to the slow clock */
if (device_may_wakeup(d) && priv->wolopts) { if (device_may_wakeup(d) && priv->wolopts) {
ret = bcmgenet_power_down(priv, GENET_POWER_WOL_MAGIC); ret = bcmgenet_power_down(priv, GENET_POWER_WOL_MAGIC);
...@@ -3678,21 +3565,17 @@ static int bcmgenet_resume(struct device *d) ...@@ -3678,21 +3565,17 @@ static int bcmgenet_resume(struct device *d)
bcmgenet_umac_reset(priv); bcmgenet_umac_reset(priv);
ret = init_umac(priv); init_umac(priv);
if (ret)
goto out_clk_disable;
/* From WOL-enabled suspend, switch to regular clock */ /* From WOL-enabled suspend, switch to regular clock */
if (priv->wolopts) if (priv->wolopts)
clk_disable_unprepare(priv->clk_wol); clk_disable_unprepare(priv->clk_wol);
phy_init_hw(priv->phydev); phy_init_hw(dev->phydev);
/* Speed settings must be restored */ /* Speed settings must be restored */
bcmgenet_mii_config(priv->dev, false); bcmgenet_mii_config(priv->dev, false);
/* disable ethernet MAC while updating its registers */
umac_enable_set(priv, CMD_TX_EN | CMD_RX_EN, false);
bcmgenet_set_hw_addr(priv, dev->dev_addr); bcmgenet_set_hw_addr(priv, dev->dev_addr);
if (priv->internal_phy) { if (priv->internal_phy) {
...@@ -3720,7 +3603,7 @@ static int bcmgenet_resume(struct device *d) ...@@ -3720,7 +3603,7 @@ static int bcmgenet_resume(struct device *d)
netif_device_attach(dev); netif_device_attach(dev);
if (!device_may_wakeup(d)) if (!device_may_wakeup(d))
phy_resume(priv->phydev); phy_resume(dev->phydev);
if (priv->eee.eee_enabled) if (priv->eee.eee_enabled)
bcmgenet_eee_enable_set(dev, true); bcmgenet_eee_enable_set(dev, true);
......
...@@ -617,7 +617,6 @@ struct bcmgenet_priv { ...@@ -617,7 +617,6 @@ struct bcmgenet_priv {
/* MDIO bus variables */ /* MDIO bus variables */
wait_queue_head_t wq; wait_queue_head_t wq;
struct phy_device *phydev;
bool internal_phy; bool internal_phy;
struct device_node *phy_dn; struct device_node *phy_dn;
struct device_node *mdio_dn; struct device_node *mdio_dn;
...@@ -711,7 +710,6 @@ int bcmgenet_mii_init(struct net_device *dev); ...@@ -711,7 +710,6 @@ int bcmgenet_mii_init(struct net_device *dev);
int bcmgenet_mii_config(struct net_device *dev, bool init); int bcmgenet_mii_config(struct net_device *dev, bool init);
int bcmgenet_mii_probe(struct net_device *dev); int bcmgenet_mii_probe(struct net_device *dev);
void bcmgenet_mii_exit(struct net_device *dev); void bcmgenet_mii_exit(struct net_device *dev);
void bcmgenet_mii_reset(struct net_device *dev);
void bcmgenet_phy_power_set(struct net_device *dev, bool enable); void bcmgenet_phy_power_set(struct net_device *dev, bool enable);
void bcmgenet_mii_setup(struct net_device *dev); void bcmgenet_mii_setup(struct net_device *dev);
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
void bcmgenet_mii_setup(struct net_device *dev) void bcmgenet_mii_setup(struct net_device *dev)
{ {
struct bcmgenet_priv *priv = netdev_priv(dev); struct bcmgenet_priv *priv = netdev_priv(dev);
struct phy_device *phydev = priv->phydev; struct phy_device *phydev = dev->phydev;
u32 reg, cmd_bits = 0; u32 reg, cmd_bits = 0;
bool status_changed = false; bool status_changed = false;
...@@ -121,22 +121,6 @@ static int bcmgenet_fixed_phy_link_update(struct net_device *dev, ...@@ -121,22 +121,6 @@ static int bcmgenet_fixed_phy_link_update(struct net_device *dev,
return 0; return 0;
} }
/* Perform a voluntary PHY software reset, since the EPHY is very finicky about
* not doing it and will start corrupting packets
*/
void bcmgenet_mii_reset(struct net_device *dev)
{
struct bcmgenet_priv *priv = netdev_priv(dev);
if (GENET_IS_V4(priv))
return;
if (priv->phydev) {
phy_init_hw(priv->phydev);
phy_start_aneg(priv->phydev);
}
}
void bcmgenet_phy_power_set(struct net_device *dev, bool enable) void bcmgenet_phy_power_set(struct net_device *dev, bool enable)
{ {
struct bcmgenet_priv *priv = netdev_priv(dev); struct bcmgenet_priv *priv = netdev_priv(dev);
...@@ -182,14 +166,14 @@ static void bcmgenet_moca_phy_setup(struct bcmgenet_priv *priv) ...@@ -182,14 +166,14 @@ static void bcmgenet_moca_phy_setup(struct bcmgenet_priv *priv)
} }
if (priv->hw_params->flags & GENET_HAS_MOCA_LINK_DET) if (priv->hw_params->flags & GENET_HAS_MOCA_LINK_DET)
fixed_phy_set_link_update(priv->phydev, fixed_phy_set_link_update(priv->dev->phydev,
bcmgenet_fixed_phy_link_update); bcmgenet_fixed_phy_link_update);
} }
int bcmgenet_mii_config(struct net_device *dev, bool init) int bcmgenet_mii_config(struct net_device *dev, bool init)
{ {
struct bcmgenet_priv *priv = netdev_priv(dev); struct bcmgenet_priv *priv = netdev_priv(dev);
struct phy_device *phydev = priv->phydev; struct phy_device *phydev = dev->phydev;
struct device *kdev = &priv->pdev->dev; struct device *kdev = &priv->pdev->dev;
const char *phy_name = NULL; const char *phy_name = NULL;
u32 id_mode_dis = 0; u32 id_mode_dis = 0;
...@@ -236,7 +220,7 @@ int bcmgenet_mii_config(struct net_device *dev, bool init) ...@@ -236,7 +220,7 @@ int bcmgenet_mii_config(struct net_device *dev, bool init)
* capabilities, use that knowledge to also configure the * capabilities, use that knowledge to also configure the
* Reverse MII interface correctly. * Reverse MII interface correctly.
*/ */
if ((priv->phydev->supported & PHY_BASIC_FEATURES) == if ((dev->phydev->supported & PHY_BASIC_FEATURES) ==
PHY_BASIC_FEATURES) PHY_BASIC_FEATURES)
port_ctrl = PORT_MODE_EXT_RVMII_25; port_ctrl = PORT_MODE_EXT_RVMII_25;
else else
...@@ -306,7 +290,7 @@ int bcmgenet_mii_probe(struct net_device *dev) ...@@ -306,7 +290,7 @@ int bcmgenet_mii_probe(struct net_device *dev)
return -ENODEV; return -ENODEV;
} }
} else { } else {
phydev = priv->phydev; phydev = dev->phydev;
phydev->dev_flags = phy_flags; phydev->dev_flags = phy_flags;
ret = phy_connect_direct(dev, phydev, bcmgenet_mii_setup, ret = phy_connect_direct(dev, phydev, bcmgenet_mii_setup,
...@@ -317,8 +301,6 @@ int bcmgenet_mii_probe(struct net_device *dev) ...@@ -317,8 +301,6 @@ int bcmgenet_mii_probe(struct net_device *dev)
} }
} }
priv->phydev = phydev;
/* Configure port multiplexer based on what the probed PHY device since /* Configure port multiplexer based on what the probed PHY device since
* reading the 'max-speed' property determines the maximum supported * reading the 'max-speed' property determines the maximum supported
* PHY speed which is needed for bcmgenet_mii_config() to configure * PHY speed which is needed for bcmgenet_mii_config() to configure
...@@ -326,7 +308,7 @@ int bcmgenet_mii_probe(struct net_device *dev) ...@@ -326,7 +308,7 @@ int bcmgenet_mii_probe(struct net_device *dev)
*/ */
ret = bcmgenet_mii_config(dev, true); ret = bcmgenet_mii_config(dev, true);
if (ret) { if (ret) {
phy_disconnect(priv->phydev); phy_disconnect(dev->phydev);
return ret; return ret;
} }
...@@ -336,7 +318,7 @@ int bcmgenet_mii_probe(struct net_device *dev) ...@@ -336,7 +318,7 @@ int bcmgenet_mii_probe(struct net_device *dev)
* Ethernet MAC ISRs * Ethernet MAC ISRs
*/ */
if (priv->internal_phy) if (priv->internal_phy)
priv->phydev->irq = PHY_IGNORE_INTERRUPT; dev->phydev->irq = PHY_IGNORE_INTERRUPT;
return 0; return 0;
} }
...@@ -545,7 +527,6 @@ static int bcmgenet_mii_pd_init(struct bcmgenet_priv *priv) ...@@ -545,7 +527,6 @@ static int bcmgenet_mii_pd_init(struct bcmgenet_priv *priv)
} }
priv->phydev = phydev;
priv->phy_interface = pd->phy_interface; priv->phy_interface = pd->phy_interface;
return 0; return 0;
......
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