net/cpsw: make sure modules remove does not leak any ressources
[firefly-linux-kernel-4.4.55.git] / drivers / net / ethernet / ti / cpsw.c
index 80cad06e5eb21337b111bc32af0376e6da2d7065..93a60e238146942a828ca3c0915460e49318331d 100644 (file)
@@ -126,6 +126,13 @@ do {                                                               \
 #define CPSW_FIFO_DUAL_MAC_MODE                (1 << 15)
 #define CPSW_FIFO_RATE_LIMIT_MODE      (2 << 15)
 
+#define CPSW_INTPACEEN         (0x3f << 16)
+#define CPSW_INTPRESCALE_MASK  (0x7FF << 0)
+#define CPSW_CMINTMAX_CNT      63
+#define CPSW_CMINTMIN_CNT      2
+#define CPSW_CMINTMAX_INTVL    (1000 / CPSW_CMINTMIN_CNT)
+#define CPSW_CMINTMIN_INTVL    ((1000 / CPSW_CMINTMAX_CNT) + 1)
+
 #define cpsw_enable_irq(priv)  \
        do {                    \
                u32 i;          \
@@ -139,6 +146,10 @@ do {                                                               \
                        disable_irq_nosync(priv->irqs_table[i]); \
        } while (0);
 
+#define cpsw_slave_index(priv)                         \
+               ((priv->data.dual_emac) ? priv->emac_port :     \
+               priv->data.active_slave)
+
 static int debug_level;
 module_param(debug_level, int, 0);
 MODULE_PARM_DESC(debug_level, "cpsw debug level (NETIF_MSG bits)");
@@ -160,6 +171,15 @@ struct cpsw_wr_regs {
        u32     rx_en;
        u32     tx_en;
        u32     misc_en;
+       u32     mem_allign1[8];
+       u32     rx_thresh_stat;
+       u32     rx_stat;
+       u32     tx_stat;
+       u32     misc_stat;
+       u32     mem_allign2[8];
+       u32     rx_imax;
+       u32     tx_imax;
+
 };
 
 struct cpsw_ss_regs {
@@ -314,6 +334,8 @@ struct cpsw_priv {
        struct cpsw_host_regs __iomem   *host_port_regs;
        u32                             msg_enable;
        u32                             version;
+       u32                             coal_intvl;
+       u32                             bus_freq_mhz;
        struct net_device_stats         stats;
        int                             rx_packet_max;
        int                             host_port;
@@ -446,62 +468,66 @@ void cpsw_tx_handler(void *token, int len, int status)
 void cpsw_rx_handler(void *token, int len, int status)
 {
        struct sk_buff          *skb = token;
+       struct sk_buff          *new_skb;
        struct net_device       *ndev = skb->dev;
        struct cpsw_priv        *priv = netdev_priv(ndev);
        int                     ret = 0;
 
        cpsw_dual_emac_src_port_detect(status, priv, ndev, skb);
 
-       /* free and bail if we are shutting down */
-       if (unlikely(!netif_running(ndev)) ||
-                       unlikely(!netif_carrier_ok(ndev))) {
+       if (unlikely(status < 0)) {
+               /* the interface is going down, skbs are purged */
                dev_kfree_skb_any(skb);
                return;
        }
-       if (likely(status >= 0)) {
+
+       new_skb = netdev_alloc_skb_ip_align(ndev, priv->rx_packet_max);
+       if (new_skb) {
                skb_put(skb, len);
                cpts_rx_timestamp(priv->cpts, skb);
                skb->protocol = eth_type_trans(skb, ndev);
                netif_receive_skb(skb);
                priv->stats.rx_bytes += len;
                priv->stats.rx_packets++;
-               skb = NULL;
-       }
-
-       if (unlikely(!netif_running(ndev))) {
-               if (skb)
-                       dev_kfree_skb_any(skb);
-               return;
+       } else {
+               priv->stats.rx_dropped++;
+               new_skb = skb;
        }
 
-       if (likely(!skb)) {
-               skb = netdev_alloc_skb_ip_align(ndev, priv->rx_packet_max);
-               if (WARN_ON(!skb))
-                       return;
-
-               ret = cpdma_chan_submit(priv->rxch, skb, skb->data,
-                                       skb_tailroom(skb), 0, GFP_KERNEL);
-       }
-       WARN_ON(ret < 0);
+       ret = cpdma_chan_submit(priv->rxch, new_skb, new_skb->data,
+                       skb_tailroom(new_skb), 0);
+       if (WARN_ON(ret < 0))
+               dev_kfree_skb_any(new_skb);
 }
 
 static irqreturn_t cpsw_interrupt(int irq, void *dev_id)
 {
        struct cpsw_priv *priv = dev_id;
+       u32 rx, tx, rx_thresh;
 
-       if (likely(netif_running(priv->ndev))) {
-               cpsw_intr_disable(priv);
-               cpsw_disable_irq(priv);
+       rx_thresh = __raw_readl(&priv->wr_regs->rx_thresh_stat);
+       rx = __raw_readl(&priv->wr_regs->rx_stat);
+       tx = __raw_readl(&priv->wr_regs->tx_stat);
+       if (!rx_thresh && !rx && !tx)
+               return IRQ_NONE;
+
+       cpsw_intr_disable(priv);
+       cpsw_disable_irq(priv);
+
+       if (netif_running(priv->ndev)) {
                napi_schedule(&priv->napi);
-       } else {
-               priv = cpsw_get_slave_priv(priv, 1);
-               if (likely(priv) && likely(netif_running(priv->ndev))) {
-                       cpsw_intr_disable(priv);
-                       cpsw_disable_irq(priv);
-                       napi_schedule(&priv->napi);
-               }
+               return IRQ_HANDLED;
+       }
+
+       priv = cpsw_get_slave_priv(priv, 1);
+       if (!priv)
+               return IRQ_NONE;
+
+       if (netif_running(priv->ndev)) {
+               napi_schedule(&priv->napi);
+               return IRQ_HANDLED;
        }
-       return IRQ_HANDLED;
+       return IRQ_NONE;
 }
 
 static int cpsw_poll(struct napi_struct *napi, int budget)
@@ -612,6 +638,77 @@ static void cpsw_adjust_link(struct net_device *ndev)
        }
 }
 
+static int cpsw_get_coalesce(struct net_device *ndev,
+                               struct ethtool_coalesce *coal)
+{
+       struct cpsw_priv *priv = netdev_priv(ndev);
+
+       coal->rx_coalesce_usecs = priv->coal_intvl;
+       return 0;
+}
+
+static int cpsw_set_coalesce(struct net_device *ndev,
+                               struct ethtool_coalesce *coal)
+{
+       struct cpsw_priv *priv = netdev_priv(ndev);
+       u32 int_ctrl;
+       u32 num_interrupts = 0;
+       u32 prescale = 0;
+       u32 addnl_dvdr = 1;
+       u32 coal_intvl = 0;
+
+       if (!coal->rx_coalesce_usecs)
+               return -EINVAL;
+
+       coal_intvl = coal->rx_coalesce_usecs;
+
+       int_ctrl =  readl(&priv->wr_regs->int_control);
+       prescale = priv->bus_freq_mhz * 4;
+
+       if (coal_intvl < CPSW_CMINTMIN_INTVL)
+               coal_intvl = CPSW_CMINTMIN_INTVL;
+
+       if (coal_intvl > CPSW_CMINTMAX_INTVL) {
+               /* Interrupt pacer works with 4us Pulse, we can
+                * throttle further by dilating the 4us pulse.
+                */
+               addnl_dvdr = CPSW_INTPRESCALE_MASK / prescale;
+
+               if (addnl_dvdr > 1) {
+                       prescale *= addnl_dvdr;
+                       if (coal_intvl > (CPSW_CMINTMAX_INTVL * addnl_dvdr))
+                               coal_intvl = (CPSW_CMINTMAX_INTVL
+                                               * addnl_dvdr);
+               } else {
+                       addnl_dvdr = 1;
+                       coal_intvl = CPSW_CMINTMAX_INTVL;
+               }
+       }
+
+       num_interrupts = (1000 * addnl_dvdr) / coal_intvl;
+       writel(num_interrupts, &priv->wr_regs->rx_imax);
+       writel(num_interrupts, &priv->wr_regs->tx_imax);
+
+       int_ctrl |= CPSW_INTPACEEN;
+       int_ctrl &= (~CPSW_INTPRESCALE_MASK);
+       int_ctrl |= (prescale & CPSW_INTPRESCALE_MASK);
+       writel(int_ctrl, &priv->wr_regs->int_control);
+
+       cpsw_notice(priv, timer, "Set coalesce to %d usecs.\n", coal_intvl);
+       if (priv->data.dual_emac) {
+               int i;
+
+               for (i = 0; i < priv->data.slaves; i++) {
+                       priv = netdev_priv(priv->slaves[i].ndev);
+                       priv->coal_intvl = coal_intvl;
+               }
+       } else {
+               priv->coal_intvl = coal_intvl;
+       }
+
+       return 0;
+}
+
 static inline int __show_stat(char *buf, int maxlen, const char *name, u32 val)
 {
        static char *leader = "........................................";
@@ -643,14 +740,14 @@ static inline int cpsw_tx_packet_submit(struct net_device *ndev,
 {
        if (!priv->data.dual_emac)
                return cpdma_chan_submit(priv->txch, skb, skb->data,
-                                 skb->len, 0, GFP_KERNEL);
+                                 skb->len, 0);
 
        if (ndev == cpsw_get_slave_ndev(priv, 0))
                return cpdma_chan_submit(priv->txch, skb, skb->data,
-                                 skb->len, 1, GFP_KERNEL);
+                                 skb->len, 1);
        else
                return cpdma_chan_submit(priv->txch, skb, skb->data,
-                                 skb->len, 2, GFP_KERNEL);
+                                 skb->len, 2);
 }
 
 static inline void cpsw_add_dual_emac_def_ale_entries(
@@ -774,6 +871,15 @@ static void cpsw_init_host_port(struct cpsw_priv *priv)
        }
 }
 
+static void cpsw_slave_stop(struct cpsw_slave *slave, struct cpsw_priv *priv)
+{
+       if (!slave->phy)
+               return;
+       phy_stop(slave->phy);
+       phy_disconnect(slave->phy);
+       slave->phy = NULL;
+}
+
 static int cpsw_ndo_open(struct net_device *ndev)
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
@@ -819,14 +925,16 @@ static int cpsw_ndo_open(struct net_device *ndev)
                        struct sk_buff *skb;
 
                        ret = -ENOMEM;
-                       skb = netdev_alloc_skb_ip_align(priv->ndev,
-                                                       priv->rx_packet_max);
+                       skb = __netdev_alloc_skb_ip_align(priv->ndev,
+                                       priv->rx_packet_max, GFP_KERNEL);
                        if (!skb)
-                               break;
+                               goto err_cleanup;
                        ret = cpdma_chan_submit(priv->rxch, skb, skb->data,
-                                       skb_tailroom(skb), 0, GFP_KERNEL);
-                       if (WARN_ON(ret < 0))
-                               break;
+                                       skb_tailroom(skb), 0);
+                       if (ret < 0) {
+                               kfree_skb(skb);
+                               goto err_cleanup;
+                       }
                }
                /* continue even if we didn't manage to submit all
                 * receive descs
@@ -834,6 +942,14 @@ static int cpsw_ndo_open(struct net_device *ndev)
                cpsw_info(priv, ifup, "submitted %d rx descriptors\n", i);
        }
 
+       /* Enable Interrupt pacing if configured */
+       if (priv->coal_intvl != 0) {
+               struct ethtool_coalesce coal;
+
+               coal.rx_coalesce_usecs = (priv->coal_intvl << 4);
+               cpsw_set_coalesce(ndev, &coal);
+       }
+
        cpdma_ctlr_start(priv->dma);
        cpsw_intr_enable(priv);
        napi_enable(&priv->napi);
@@ -843,15 +959,13 @@ static int cpsw_ndo_open(struct net_device *ndev)
        if (priv->data.dual_emac)
                priv->slaves[priv->emac_port].open_stat = true;
        return 0;
-}
 
-static void cpsw_slave_stop(struct cpsw_slave *slave, struct cpsw_priv *priv)
-{
-       if (!slave->phy)
-               return;
-       phy_stop(slave->phy);
-       phy_disconnect(slave->phy);
-       slave->phy = NULL;
+err_cleanup:
+       cpdma_ctlr_stop(priv->dma);
+       for_each_slave(priv, cpsw_slave_stop, priv);
+       pm_runtime_put_sync(&priv->pdev->dev);
+       netif_carrier_off(priv->ndev);
+       return ret;
 }
 
 static int cpsw_ndo_stop(struct net_device *ndev)
@@ -942,7 +1056,7 @@ static void cpsw_ndo_change_rx_flags(struct net_device *ndev, int flags)
 
 static void cpsw_hwtstamp_v1(struct cpsw_priv *priv)
 {
-       struct cpsw_slave *slave = &priv->slaves[priv->data.cpts_active_slave];
+       struct cpsw_slave *slave = &priv->slaves[priv->data.active_slave];
        u32 ts_en, seq_id;
 
        if (!priv->cpts->tx_enable && !priv->cpts->rx_enable) {
@@ -971,7 +1085,7 @@ static void cpsw_hwtstamp_v2(struct cpsw_priv *priv)
        if (priv->data.dual_emac)
                slave = &priv->slaves[priv->emac_port];
        else
-               slave = &priv->slaves[priv->data.cpts_active_slave];
+               slave = &priv->slaves[priv->data.active_slave];
 
        ctrl = slave_read(slave, CPSW2_CONTROL);
        ctrl &= ~CTRL_ALL_TS_MASK;
@@ -1056,14 +1170,26 @@ static int cpsw_hwtstamp_ioctl(struct net_device *dev, struct ifreq *ifr)
 
 static int cpsw_ndo_ioctl(struct net_device *dev, struct ifreq *req, int cmd)
 {
+       struct cpsw_priv *priv = netdev_priv(dev);
+       struct mii_ioctl_data *data = if_mii(req);
+       int slave_no = cpsw_slave_index(priv);
+
        if (!netif_running(dev))
                return -EINVAL;
 
+       switch (cmd) {
 #ifdef CONFIG_TI_CPTS
-       if (cmd == SIOCSHWTSTAMP)
+       case SIOCSHWTSTAMP:
                return cpsw_hwtstamp_ioctl(dev, req);
 #endif
-       return -ENOTSUPP;
+       case SIOCGMIIPHY:
+               data->phy_id = priv->slaves[slave_no].phy->addr;
+               break;
+       default:
+               return -ENOTSUPP;
+       }
+
+       return 0;
 }
 
 static void cpsw_ndo_tx_timeout(struct net_device *ndev)
@@ -1138,7 +1264,7 @@ clean_vid:
 }
 
 static int cpsw_ndo_vlan_rx_add_vid(struct net_device *ndev,
-               unsigned short vid)
+                                   __be16 proto, u16 vid)
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
 
@@ -1150,7 +1276,7 @@ static int cpsw_ndo_vlan_rx_add_vid(struct net_device *ndev,
 }
 
 static int cpsw_ndo_vlan_rx_kill_vid(struct net_device *ndev,
-               unsigned short vid)
+                                    __be16 proto, u16 vid)
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
        int ret;
@@ -1244,12 +1370,39 @@ static int cpsw_get_ts_info(struct net_device *ndev,
        return 0;
 }
 
+static int cpsw_get_settings(struct net_device *ndev,
+                            struct ethtool_cmd *ecmd)
+{
+       struct cpsw_priv *priv = netdev_priv(ndev);
+       int slave_no = cpsw_slave_index(priv);
+
+       if (priv->slaves[slave_no].phy)
+               return phy_ethtool_gset(priv->slaves[slave_no].phy, ecmd);
+       else
+               return -EOPNOTSUPP;
+}
+
+static int cpsw_set_settings(struct net_device *ndev, struct ethtool_cmd *ecmd)
+{
+       struct cpsw_priv *priv = netdev_priv(ndev);
+       int slave_no = cpsw_slave_index(priv);
+
+       if (priv->slaves[slave_no].phy)
+               return phy_ethtool_sset(priv->slaves[slave_no].phy, ecmd);
+       else
+               return -EOPNOTSUPP;
+}
+
 static const struct ethtool_ops cpsw_ethtool_ops = {
        .get_drvinfo    = cpsw_get_drvinfo,
        .get_msglevel   = cpsw_get_msglevel,
        .set_msglevel   = cpsw_set_msglevel,
        .get_link       = ethtool_op_get_link,
        .get_ts_info    = cpsw_get_ts_info,
+       .get_settings   = cpsw_get_settings,
+       .set_settings   = cpsw_set_settings,
+       .get_coalesce   = cpsw_get_coalesce,
+       .set_coalesce   = cpsw_set_coalesce,
 };
 
 static void cpsw_slave_init(struct cpsw_slave *slave, struct cpsw_priv *priv,
@@ -1282,12 +1435,12 @@ static int cpsw_probe_dt(struct cpsw_platform_data *data,
        }
        data->slaves = prop;
 
-       if (of_property_read_u32(node, "cpts_active_slave", &prop)) {
-               pr_err("Missing cpts_active_slave property in the DT.\n");
+       if (of_property_read_u32(node, "active_slave", &prop)) {
+               pr_err("Missing active_slave property in the DT.\n");
                ret = -EINVAL;
                goto error_ret;
        }
-       data->cpts_active_slave = prop;
+       data->active_slave = prop;
 
        if (of_property_read_u32(node, "cpts_clock_mult", &prop)) {
                pr_err("Missing cpts_clock_mult property in the DT.\n");
@@ -1380,7 +1533,7 @@ static int cpsw_probe_dt(struct cpsw_platform_data *data,
                        memcpy(slave_data->mac_addr, mac_addr, ETH_ALEN);
 
                if (data->dual_emac) {
-                       if (of_property_read_u32(node, "dual_emac_res_vlan",
+                       if (of_property_read_u32(slave_node, "dual_emac_res_vlan",
                                                 &prop)) {
                                pr_err("Missing dual_emac_res_vlan in DT.\n");
                                slave_data->dual_emac_res_vlan = i+1;
@@ -1437,6 +1590,9 @@ static int cpsw_probe_dual_emac(struct platform_device *pdev,
        priv_sl2->slaves = priv->slaves;
        priv_sl2->clk = priv->clk;
 
+       priv_sl2->coal_intvl = 0;
+       priv_sl2->bus_freq_mhz = priv->bus_freq_mhz;
+
        priv_sl2->cpsw_res = priv->cpsw_res;
        priv_sl2->regs = priv->regs;
        priv_sl2->host_port = priv->host_port;
@@ -1456,7 +1612,7 @@ static int cpsw_probe_dual_emac(struct platform_device *pdev,
                priv_sl2->num_irqs = priv->num_irqs;
        }
 
-       ndev->features |= NETIF_F_HW_VLAN_FILTER;
+       ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
 
        ndev->netdev_ops = &cpsw_netdev_ops;
        SET_ETHTOOL_OPS(ndev, &cpsw_ethtool_ops);
@@ -1476,7 +1632,7 @@ static int cpsw_probe_dual_emac(struct platform_device *pdev,
 
 static int cpsw_probe(struct platform_device *pdev)
 {
-       struct cpsw_platform_data       *data = pdev->dev.platform_data;
+       struct cpsw_platform_data       *data;
        struct net_device               *ndev;
        struct cpsw_priv                *priv;
        struct cpdma_params             dma_params;
@@ -1546,6 +1702,8 @@ static int cpsw_probe(struct platform_device *pdev)
                ret = -ENODEV;
                goto clean_slave_ret;
        }
+       priv->coal_intvl = 0;
+       priv->bus_freq_mhz = clk_get_rate(priv->clk) / 1000000;
 
        priv->cpsw_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!priv->cpsw_res) {
@@ -1687,12 +1845,12 @@ static int cpsw_probe(struct platform_device *pdev)
                                goto clean_ale_ret;
                        }
                        priv->irqs_table[k] = i;
-                       priv->num_irqs = k;
+                       priv->num_irqs = k + 1;
                }
                k++;
        }
 
-       ndev->features |= NETIF_F_HW_VLAN_FILTER;
+       ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
 
        ndev->netdev_ops = &cpsw_netdev_ops;
        SET_ETHTOOL_OPS(ndev, &cpsw_ethtool_ops);
@@ -1725,7 +1883,8 @@ static int cpsw_probe(struct platform_device *pdev)
        return 0;
 
 clean_irq_ret:
-       free_irq(ndev->irq, priv);
+       for (i = 0; i < priv->num_irqs; i++)
+               free_irq(priv->irqs_table[i], priv);
 clean_ale_ret:
        cpsw_ale_destroy(priv->ale);
 clean_dma_ret:
@@ -1748,7 +1907,8 @@ clean_slave_ret:
        pm_runtime_disable(&pdev->dev);
        kfree(priv->slaves);
 clean_ndev_ret:
-       free_netdev(ndev);
+       kfree(priv->data.slave_data);
+       free_netdev(priv->ndev);
        return ret;
 }
 
@@ -1756,12 +1916,17 @@ static int cpsw_remove(struct platform_device *pdev)
 {
        struct net_device *ndev = platform_get_drvdata(pdev);
        struct cpsw_priv *priv = netdev_priv(ndev);
+       int i;
 
-       pr_info("removing device");
        platform_set_drvdata(pdev, NULL);
+       if (priv->data.dual_emac)
+               unregister_netdev(cpsw_get_slave_ndev(priv, 1));
+       unregister_netdev(ndev);
 
        cpts_unregister(priv->cpts);
-       free_irq(ndev->irq, priv);
+       for (i = 0; i < priv->num_irqs; i++)
+               free_irq(priv->irqs_table[i], priv);
+
        cpsw_ale_destroy(priv->ale);
        cpdma_chan_destroy(priv->txch);
        cpdma_chan_destroy(priv->rxch);
@@ -1775,8 +1940,10 @@ static int cpsw_remove(struct platform_device *pdev)
        pm_runtime_disable(&pdev->dev);
        clk_put(priv->clk);
        kfree(priv->slaves);
+       kfree(priv->data.slave_data);
+       if (priv->data.dual_emac)
+               free_netdev(cpsw_get_slave_ndev(priv, 1));
        free_netdev(ndev);
-
        return 0;
 }
 
@@ -1812,6 +1979,7 @@ static const struct of_device_id cpsw_of_mtable[] = {
        { .compatible = "ti,cpsw", },
        { /* sentinel */ },
 };
+MODULE_DEVICE_TABLE(of, cpsw_of_mtable);
 
 static struct platform_driver cpsw_driver = {
        .driver = {