summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/net/ethernet/freescale/fec.h3
-rw-r--r--drivers/net/ethernet/freescale/fec_main.c196
2 files changed, 146 insertions, 53 deletions
diff --git a/drivers/net/ethernet/freescale/fec.h b/drivers/net/ethernet/freescale/fec.h
index ec4c76264eb9..6f652ec2a037 100644
--- a/drivers/net/ethernet/freescale/fec.h
+++ b/drivers/net/ethernet/freescale/fec.h
@@ -321,7 +321,8 @@ struct fec_enet_private {
struct completion mdio_done;
int irq[FEC_IRQ_NUM];
int bufdesc_ex;
- int pause_flag;
+ unsigned short pause_flag;
+ unsigned short pause_mode;
struct napi_struct napi;
int csum_flags;
diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c
index 1ab6388abc20..b79d78fd6e7a 100644
--- a/drivers/net/ethernet/freescale/fec_main.c
+++ b/drivers/net/ethernet/freescale/fec_main.c
@@ -227,8 +227,9 @@ MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address");
/* Transmitter timeout */
#define TX_TIMEOUT (2 * HZ)
-#define FEC_PAUSE_FLAG_AUTONEG 0x1
-#define FEC_PAUSE_FLAG_ENABLE 0x2
+#define FEC_PAUSE_FLAG_AUTONEG BIT(0)
+#define FEC_PAUSE_FLAG_RX BIT(1)
+#define FEC_PAUSE_FLAG_TX BIT(2)
#define TSO_HEADER_SIZE 128
/* Max number of allowed TCP segments for software TSO */
@@ -927,7 +928,7 @@ fec_restart(struct net_device *ndev)
*/
if (id_entry->driver_data & FEC_QUIRK_ENET_MAC) {
/* Enable flow control and length check */
- rcntl |= 0x40000000 | 0x00000020;
+ rcntl |= 0x40000000;
/* RGMII, RMII or MII */
if (fep->phy_interface == PHY_INTERFACE_MODE_RGMII)
@@ -973,22 +974,24 @@ fec_restart(struct net_device *ndev)
}
#if !defined(CONFIG_M5272)
- /* enable pause frame*/
- if ((fep->pause_flag & FEC_PAUSE_FLAG_ENABLE) ||
- ((fep->pause_flag & FEC_PAUSE_FLAG_AUTONEG) &&
- fep->phy_dev && fep->phy_dev->pause)) {
- rcntl |= FEC_ENET_FCE;
-
- /* set FIFO threshold parameter to reduce overrun */
- writel(FEC_ENET_RSEM_V, fep->hwp + FEC_R_FIFO_RSEM);
- writel(FEC_ENET_RSFL_V, fep->hwp + FEC_R_FIFO_RSFL);
- writel(FEC_ENET_RAEM_V, fep->hwp + FEC_R_FIFO_RAEM);
- writel(FEC_ENET_RAFL_V, fep->hwp + FEC_R_FIFO_RAFL);
-
- /* OPD */
- writel(FEC_ENET_OPD_V, fep->hwp + FEC_OPD);
- } else {
- rcntl &= ~FEC_ENET_FCE;
+ if (fep->full_duplex == DUPLEX_FULL) {
+ /*
+ * Configure pause modes according to the current status.
+ * Must only be enabled for full duplex links.
+ */
+ if (fep->pause_mode & FEC_PAUSE_FLAG_RX)
+ rcntl |= FEC_ENET_FCE;
+
+ if (fep->pause_mode & FEC_PAUSE_FLAG_TX) {
+ /* set FIFO threshold parameter to reduce overrun */
+ writel(FEC_ENET_RSEM_V, fep->hwp + FEC_R_FIFO_RSEM);
+ writel(FEC_ENET_RSFL_V, fep->hwp + FEC_R_FIFO_RSFL);
+ writel(FEC_ENET_RAEM_V, fep->hwp + FEC_R_FIFO_RAEM);
+ writel(FEC_ENET_RAFL_V, fep->hwp + FEC_R_FIFO_RAFL);
+
+ /* OPD */
+ writel(FEC_ENET_OPD_V, fep->hwp + FEC_OPD);
+ }
}
#endif /* !defined(CONFIG_M5272) */
@@ -1543,6 +1546,35 @@ static void fec_enet_adjust_link(struct net_device *ndev)
status_change = 1;
}
+ if (fep->pause_flag & FEC_PAUSE_FLAG_AUTONEG) {
+ u32 lcl_adv = phy_dev->advertising;
+ u32 rmt_adv = phy_dev->lp_advertising;
+ unsigned mode = 0;
+
+ if (lcl_adv & rmt_adv & ADVERTISED_Pause) {
+ /*
+ * Local Device Link Partner
+ * Pause AsymDir Pause AsymDir Result
+ * 1 X 1 X TX+RX
+ */
+ mode = FEC_PAUSE_FLAG_TX | FEC_PAUSE_FLAG_RX;
+ } else if (lcl_adv & rmt_adv & ADVERTISED_Asym_Pause) {
+ /*
+ * 0 1 1 1 RX
+ * 1 1 0 1 TX
+ */
+ if (rmt_adv & ADVERTISED_Pause)
+ mode = FEC_PAUSE_FLAG_RX;
+ else
+ mode = FEC_PAUSE_FLAG_TX;
+ }
+
+ if (mode != fep->pause_mode) {
+ fep->pause_mode = mode;
+ status_change = 1;
+ }
+ }
+
/* if any of the above changed restart the FEC */
if (status_change) {
napi_disable(&fep->napi);
@@ -1674,6 +1706,34 @@ failed_clk_ipg:
return ret;
}
+static void fec_enet_phy_config(struct net_device *ndev)
+{
+#ifndef CONFIG_M5272
+ struct fec_enet_private *fep = netdev_priv(ndev);
+ struct phy_device *phy = fep->phy_dev;
+ unsigned pause = 0;
+
+ /*
+ * Pause advertisment logic is weird. We don't advertise the raw
+ * "can tx" and "can rx" modes, but instead it is whether we support
+ * symmetric flow or asymmetric flow.
+ *
+ * Symmetric flow means we can only support both transmit and receive
+ * flow control frames together. Asymmetric flow means we can
+ * independently control each. Note that there is no bit encoding
+ * for "I can only receive flow control frames."
+ */
+ if (fep->pause_flag & FEC_PAUSE_FLAG_RX)
+ pause |= ADVERTISED_Asym_Pause | ADVERTISED_Pause;
+ if (fep->pause_flag & FEC_PAUSE_FLAG_TX)
+ pause |= ADVERTISED_Asym_Pause;
+
+ pause &= phy->supported;
+ pause |= phy->advertising & ~(ADVERTISED_Pause | ADVERTISED_Asym_Pause);
+ phy->advertising = pause;
+#endif
+}
+
static int fec_enet_mii_probe(struct net_device *ndev)
{
struct fec_enet_private *fep = netdev_priv(ndev);
@@ -1728,7 +1788,7 @@ static int fec_enet_mii_probe(struct net_device *ndev)
phy_dev->supported &= PHY_GBIT_FEATURES;
phy_dev->supported &= ~SUPPORTED_1000baseT_Half;
#if !defined(CONFIG_M5272)
- phy_dev->supported |= SUPPORTED_Pause;
+ phy_dev->supported |= SUPPORTED_Pause | SUPPORTED_Asym_Pause;
#endif
}
else
@@ -1740,6 +1800,8 @@ static int fec_enet_mii_probe(struct net_device *ndev)
fep->link = 0;
fep->full_duplex = 0;
+ fec_enet_phy_config(ndev);
+
netdev_info(ndev, "Freescale FEC PHY driver [%s] (mii_bus:phy_addr=%s, irq=%d)\n",
fep->phy_dev->drv->name, dev_name(&fep->phy_dev->dev),
fep->phy_dev->irq);
@@ -1930,50 +1992,78 @@ static void fec_enet_get_pauseparam(struct net_device *ndev,
struct fec_enet_private *fep = netdev_priv(ndev);
pause->autoneg = (fep->pause_flag & FEC_PAUSE_FLAG_AUTONEG) != 0;
- pause->tx_pause = (fep->pause_flag & FEC_PAUSE_FLAG_ENABLE) != 0;
- pause->rx_pause = pause->tx_pause;
+ pause->rx_pause = (fep->pause_flag & FEC_PAUSE_FLAG_RX) != 0;
+ pause->tx_pause = (fep->pause_flag & FEC_PAUSE_FLAG_TX) != 0;
}
static int fec_enet_set_pauseparam(struct net_device *ndev,
struct ethtool_pauseparam *pause)
{
struct fec_enet_private *fep = netdev_priv(ndev);
+ unsigned pause_flag, changed;
+ struct phy_device *phy = fep->phy_dev;
- if (!fep->phy_dev)
+ if (!phy)
return -ENODEV;
-
- if (pause->tx_pause != pause->rx_pause) {
- netdev_info(ndev,
- "hardware only support enable/disable both tx and rx");
+ if (!(phy->supported & SUPPORTED_Pause))
+ return -EINVAL;
+ if (!(phy->supported & SUPPORTED_Asym_Pause) &&
+ pause->rx_pause != pause->tx_pause)
return -EINVAL;
- }
- fep->pause_flag = 0;
+ pause_flag = 0;
+ if (pause->autoneg)
+ pause_flag |= FEC_PAUSE_FLAG_AUTONEG;
+ if (pause->rx_pause)
+ pause_flag |= FEC_PAUSE_FLAG_RX;
+ if (pause->tx_pause)
+ pause_flag |= FEC_PAUSE_FLAG_TX;
- /* tx pause must be same as rx pause */
- fep->pause_flag |= pause->rx_pause ? FEC_PAUSE_FLAG_ENABLE : 0;
- fep->pause_flag |= pause->autoneg ? FEC_PAUSE_FLAG_AUTONEG : 0;
+ changed = fep->pause_flag ^ pause_flag;
+ fep->pause_flag = pause_flag;
- if (pause->rx_pause || pause->autoneg) {
- fep->phy_dev->supported |= ADVERTISED_Pause;
- fep->phy_dev->advertising |= ADVERTISED_Pause;
- } else {
- fep->phy_dev->supported &= ~ADVERTISED_Pause;
- fep->phy_dev->advertising &= ~ADVERTISED_Pause;
- }
+ /* configure the phy advertisment according to our new options */
+ fec_enet_phy_config(ndev);
- if (pause->autoneg) {
- if (netif_running(ndev))
- fec_stop(ndev);
- phy_start_aneg(fep->phy_dev);
- }
- if (netif_running(ndev)) {
- napi_disable(&fep->napi);
- netif_tx_lock_bh(ndev);
- fec_restart(ndev);
- netif_wake_queue(ndev);
- netif_tx_unlock_bh(ndev);
- napi_enable(&fep->napi);
+ if (changed) {
+ if (pause_flag & FEC_PAUSE_FLAG_AUTONEG) {
+ if (netif_running(ndev))
+ phy_start_aneg(fep->phy_dev);
+ } else {
+ int adv, old_adv;
+
+ /*
+ * Even if we are not in autonegotiate mode, we
+ * still update the phy with our capabilities so
+ * our link parter can make the appropriate
+ * decision. PHYLIB provides no way to do this.
+ */
+ adv = phy_read(phy, MII_ADVERTISE);
+ if (adv >= 0) {
+ old_adv = adv;
+ adv &= ~(ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
+ if (phy->advertising & ADVERTISED_Pause)
+ adv |= ADVERTISE_PAUSE_CAP;
+ if (phy->advertising & ADVERTISED_Asym_Pause)
+ adv |= ADVERTISE_PAUSE_ASYM;
+
+ if (old_adv != adv)
+ phy_write(phy, MII_ADVERTISE, adv);
+ }
+
+ /* Forced pause mode */
+ fep->pause_mode = fep->pause_flag;
+
+ if (netif_running(ndev)) {
+ napi_disable(&fep->napi);
+ netif_tx_lock_bh(ndev);
+ fec_stop(ndev);
+ fec_restart(ndev);
+ netif_wake_queue(ndev);
+ netif_tx_unlock_bh(ndev);
+ napi_enable(&fep->napi);
+ }
+ }
}
return 0;
@@ -2606,7 +2696,9 @@ fec_probe(struct platform_device *pdev)
/* default enable pause frame auto negotiation */
if (pdev->id_entry &&
(pdev->id_entry->driver_data & FEC_QUIRK_HAS_GBIT))
- fep->pause_flag |= FEC_PAUSE_FLAG_AUTONEG;
+ fep->pause_flag |= FEC_PAUSE_FLAG_AUTONEG |
+ FEC_PAUSE_FLAG_TX |
+ FEC_PAUSE_FLAG_RX;
#endif
/* Select default pin state */