net: fec: add interrupt coalescence feature support
authorFugang Duan <B38611@freescale.com>
Tue, 16 Sep 2014 21:18:52 +0000 (05:18 +0800)
committerDavid S. Miller <davem@davemloft.net>
Fri, 19 Sep 2014 19:36:50 +0000 (15:36 -0400)
i.MX6 SX support interrupt coalescence feature
By default, init the interrupt coalescing frame count threshold and
timer threshold.

Supply the ethtool interfaces as below for user tuning to improve
enet performance:
rx_max_coalesced_frames
rx_coalesce_usecs
tx_max_coalesced_frames
tx_coalesce_usecs

Signed-off-by: Fugang Duan <B38611@freescale.com>
Signed-off-by: Frank Li <Frank.Li@freescale.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/freescale/fec.h
drivers/net/ethernet/freescale/fec_main.c

index e1bcb4fc244a96e7aa8621c059408796758e2b38..e4eb89742ece2db145deecebf21f1a7a18066080 100644 (file)
@@ -355,6 +355,14 @@ struct bufdesc_ex {
 #define FEC_DEFAULT_IMASK (FEC_ENET_TXF | FEC_ENET_RXF | FEC_ENET_MII | FEC_ENET_TS_TIMER)
 #define FEC_RX_DISABLED_IMASK (FEC_DEFAULT_IMASK & (~FEC_ENET_RXF))
 
+/* ENET interrupt coalescing macro define */
+#define FEC_ITR_CLK_SEL                (0x1 << 30)
+#define FEC_ITR_EN             (0x1 << 31)
+#define FEC_ITR_ICFT(X)                ((X & 0xFF) << 20)
+#define FEC_ITR_ICTT(X)                ((X) & 0xFFFF)
+#define FEC_ITR_ICFT_DEFAULT   200  /* Set 200 frame count threshold */
+#define FEC_ITR_ICTT_DEFAULT   1000 /* Set 1000us timer threshold */
+
 #define FEC_VLAN_TAG_LEN       0x04
 #define FEC_ETHTYPE_LEN                0x02
 
@@ -466,6 +474,13 @@ struct fec_enet_private {
 
        unsigned int tx_align;
        unsigned int rx_align;
+
+       /* hw interrupt coalesce */
+       unsigned int rx_pkts_itr;
+       unsigned int rx_time_itr;
+       unsigned int tx_pkts_itr;
+       unsigned int tx_time_itr;
+       unsigned int itr_clk_rate;
 };
 
 void fec_ptp_init(struct platform_device *pdev);
index 9772af6ff5c20a0b00450b7c968b0b65496de3a0..6d4e09b695535293196101262fbee9132ee2f9dc 100644 (file)
@@ -63,6 +63,7 @@
 #include "fec.h"
 
 static void set_multicast_list(struct net_device *ndev);
+static void fec_enet_itr_coal_init(struct net_device *ndev);
 
 #define DRIVER_NAME    "fec"
 
@@ -1095,6 +1096,10 @@ fec_restart(struct net_device *ndev)
 
        /* Enable interrupts we wish to service */
        writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
+
+       /* Init the interrupt coalescing */
+       fec_enet_itr_coal_init(ndev);
+
 }
 
 static void
@@ -2234,12 +2239,141 @@ static int fec_enet_nway_reset(struct net_device *dev)
        return genphy_restart_aneg(phydev);
 }
 
+/* ITR clock source is enet system clock (clk_ahb).
+ * TCTT unit is cycle_ns * 64 cycle
+ * So, the ICTT value = X us / (cycle_ns * 64)
+ */
+static int fec_enet_us_to_itr_clock(struct net_device *ndev, int us)
+{
+       struct fec_enet_private *fep = netdev_priv(ndev);
+
+       return us * (fep->itr_clk_rate / 64000) / 1000;
+}
+
+/* Set threshold for interrupt coalescing */
+static void fec_enet_itr_coal_set(struct net_device *ndev)
+{
+       struct fec_enet_private *fep = netdev_priv(ndev);
+       const struct platform_device_id *id_entry =
+                               platform_get_device_id(fep->pdev);
+       int rx_itr, tx_itr;
+
+       if (!(id_entry->driver_data & FEC_QUIRK_HAS_AVB))
+               return;
+
+       /* Must be greater than zero to avoid unpredictable behavior */
+       if (!fep->rx_time_itr || !fep->rx_pkts_itr ||
+           !fep->tx_time_itr || !fep->tx_pkts_itr)
+               return;
+
+       /* Select enet system clock as Interrupt Coalescing
+        * timer Clock Source
+        */
+       rx_itr = FEC_ITR_CLK_SEL;
+       tx_itr = FEC_ITR_CLK_SEL;
+
+       /* set ICFT and ICTT */
+       rx_itr |= FEC_ITR_ICFT(fep->rx_pkts_itr);
+       rx_itr |= FEC_ITR_ICTT(fec_enet_us_to_itr_clock(ndev, fep->rx_time_itr));
+       tx_itr |= FEC_ITR_ICFT(fep->tx_pkts_itr);
+       tx_itr |= FEC_ITR_ICTT(fec_enet_us_to_itr_clock(ndev, fep->tx_time_itr));
+
+       rx_itr |= FEC_ITR_EN;
+       tx_itr |= FEC_ITR_EN;
+
+       writel(tx_itr, fep->hwp + FEC_TXIC0);
+       writel(rx_itr, fep->hwp + FEC_RXIC0);
+       writel(tx_itr, fep->hwp + FEC_TXIC1);
+       writel(rx_itr, fep->hwp + FEC_RXIC1);
+       writel(tx_itr, fep->hwp + FEC_TXIC2);
+       writel(rx_itr, fep->hwp + FEC_RXIC2);
+}
+
+static int
+fec_enet_get_coalesce(struct net_device *ndev, struct ethtool_coalesce *ec)
+{
+       struct fec_enet_private *fep = netdev_priv(ndev);
+       const struct platform_device_id *id_entry =
+                               platform_get_device_id(fep->pdev);
+
+       if (!(id_entry->driver_data & FEC_QUIRK_HAS_AVB))
+               return -EOPNOTSUPP;
+
+       ec->rx_coalesce_usecs = fep->rx_time_itr;
+       ec->rx_max_coalesced_frames = fep->rx_pkts_itr;
+
+       ec->tx_coalesce_usecs = fep->tx_time_itr;
+       ec->tx_max_coalesced_frames = fep->tx_pkts_itr;
+
+       return 0;
+}
+
+static int
+fec_enet_set_coalesce(struct net_device *ndev, struct ethtool_coalesce *ec)
+{
+       struct fec_enet_private *fep = netdev_priv(ndev);
+       const struct platform_device_id *id_entry =
+                               platform_get_device_id(fep->pdev);
+
+       unsigned int cycle;
+
+       if (!(id_entry->driver_data & FEC_QUIRK_HAS_AVB))
+               return -EOPNOTSUPP;
+
+       if (ec->rx_max_coalesced_frames > 255) {
+               pr_err("Rx coalesced frames exceed hardware limiation");
+               return -EINVAL;
+       }
+
+       if (ec->tx_max_coalesced_frames > 255) {
+               pr_err("Tx coalesced frame exceed hardware limiation");
+               return -EINVAL;
+       }
+
+       cycle = fec_enet_us_to_itr_clock(ndev, fep->rx_time_itr);
+       if (cycle > 0xFFFF) {
+               pr_err("Rx coalesed usec exceeed hardware limiation");
+               return -EINVAL;
+       }
+
+       cycle = fec_enet_us_to_itr_clock(ndev, fep->tx_time_itr);
+       if (cycle > 0xFFFF) {
+               pr_err("Rx coalesed usec exceeed hardware limiation");
+               return -EINVAL;
+       }
+
+       fep->rx_time_itr = ec->rx_coalesce_usecs;
+       fep->rx_pkts_itr = ec->rx_max_coalesced_frames;
+
+       fep->tx_time_itr = ec->tx_coalesce_usecs;
+       fep->tx_pkts_itr = ec->tx_max_coalesced_frames;
+
+       fec_enet_itr_coal_set(ndev);
+
+       return 0;
+}
+
+static void fec_enet_itr_coal_init(struct net_device *ndev)
+{
+       struct ethtool_coalesce ec;
+
+       ec.rx_coalesce_usecs = FEC_ITR_ICTT_DEFAULT;
+       ec.rx_max_coalesced_frames = FEC_ITR_ICFT_DEFAULT;
+
+       ec.tx_coalesce_usecs = FEC_ITR_ICTT_DEFAULT;
+       ec.tx_max_coalesced_frames = FEC_ITR_ICFT_DEFAULT;
+
+       fec_enet_set_coalesce(ndev, &ec);
+}
+
 static const struct ethtool_ops fec_enet_ethtool_ops = {
        .get_settings           = fec_enet_get_settings,
        .set_settings           = fec_enet_set_settings,
        .get_drvinfo            = fec_enet_get_drvinfo,
        .nway_reset             = fec_enet_nway_reset,
        .get_link               = ethtool_op_get_link,
+       .get_coalesce           = fec_enet_get_coalesce,
+       .set_coalesce           = fec_enet_set_coalesce,
 #ifndef CONFIG_M5272
        .get_pauseparam         = fec_enet_get_pauseparam,
        .set_pauseparam         = fec_enet_set_pauseparam,
@@ -3006,6 +3140,8 @@ fec_probe(struct platform_device *pdev)
                goto failed_clk;
        }
 
+       fep->itr_clk_rate = clk_get_rate(fep->clk_ahb);
+
        /* enet_out is optional, depends on board */
        fep->clk_enet_out = devm_clk_get(&pdev->dev, "enet_out");
        if (IS_ERR(fep->clk_enet_out))