Merge branch 'fixes' of git://ftp.arm.linux.org.uk/~rmk/linux-arm
[cascardo/linux.git] / drivers / net / ethernet / ti / cpsw.c
index e2a0028..952e1e4 100644 (file)
@@ -33,6 +33,8 @@
 #include <linux/of_net.h>
 #include <linux/of_device.h>
 #include <linux/if_vlan.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
 
 #include <linux/pinctrl/consumer.h>
 
@@ -397,6 +399,8 @@ struct cpsw_priv {
        struct cpdma_ctlr               *dma;
        struct cpdma_chan               *txch, *rxch;
        struct cpsw_ale                 *ale;
+       bool                            rx_pause;
+       bool                            tx_pause;
        /* snapshot of IRQ numbers */
        u32 irqs_table[4];
        u32 num_irqs;
@@ -855,6 +859,12 @@ static void _cpsw_adjust_link(struct cpsw_slave *slave,
                else if (phy->speed == 10)
                        mac_control |= BIT(18); /* In Band mode */
 
+               if (priv->rx_pause)
+                       mac_control |= BIT(3);
+
+               if (priv->tx_pause)
+                       mac_control |= BIT(4);
+
                *link = true;
        } else {
                mac_control = 0;
@@ -1246,6 +1256,9 @@ static int cpsw_ndo_open(struct net_device *ndev)
                /* enable statistics collection only on all ports */
                __raw_writel(0x7, &priv->regs->stat_port_en);
 
+               /* Enable internal fifo flow control */
+               writel(0x7, &priv->regs->flow_control);
+
                if (WARN_ON(!priv->data.rx_descs))
                        priv->data.rx_descs = 128;
 
@@ -1807,6 +1820,30 @@ static int cpsw_set_wol(struct net_device *ndev, struct ethtool_wolinfo *wol)
                return -EOPNOTSUPP;
 }
 
+static void cpsw_get_pauseparam(struct net_device *ndev,
+                               struct ethtool_pauseparam *pause)
+{
+       struct cpsw_priv *priv = netdev_priv(ndev);
+
+       pause->autoneg = AUTONEG_DISABLE;
+       pause->rx_pause = priv->rx_pause ? true : false;
+       pause->tx_pause = priv->tx_pause ? true : false;
+}
+
+static int cpsw_set_pauseparam(struct net_device *ndev,
+                              struct ethtool_pauseparam *pause)
+{
+       struct cpsw_priv *priv = netdev_priv(ndev);
+       bool link;
+
+       priv->rx_pause = pause->rx_pause ? true : false;
+       priv->tx_pause = pause->tx_pause ? true : false;
+
+       for_each_slave(priv, _cpsw_adjust_link, priv, &link);
+
+       return 0;
+}
+
 static const struct ethtool_ops cpsw_ethtool_ops = {
        .get_drvinfo    = cpsw_get_drvinfo,
        .get_msglevel   = cpsw_get_msglevel,
@@ -1820,6 +1857,8 @@ static const struct ethtool_ops cpsw_ethtool_ops = {
        .get_sset_count         = cpsw_get_sset_count,
        .get_strings            = cpsw_get_strings,
        .get_ethtool_stats      = cpsw_get_ethtool_stats,
+       .get_pauseparam         = cpsw_get_pauseparam,
+       .set_pauseparam         = cpsw_set_pauseparam,
        .get_wol        = cpsw_get_wol,
        .set_wol        = cpsw_set_wol,
        .get_regs_len   = cpsw_get_regs_len,
@@ -1839,6 +1878,36 @@ static void cpsw_slave_init(struct cpsw_slave *slave, struct cpsw_priv *priv,
        slave->port_vlan = data->dual_emac_res_vlan;
 }
 
+#define AM33XX_CTRL_MAC_LO_REG(id) (0x630 + 0x8 * id)
+#define AM33XX_CTRL_MAC_HI_REG(id) (0x630 + 0x8 * id + 0x4)
+
+static int cpsw_am33xx_cm_get_macid(struct device *dev, int slave,
+               u8 *mac_addr)
+{
+       u32 macid_lo;
+       u32 macid_hi;
+       struct regmap *syscon;
+
+       syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon");
+       if (IS_ERR(syscon)) {
+               if (PTR_ERR(syscon) == -ENODEV)
+                       return 0;
+               return PTR_ERR(syscon);
+       }
+
+       regmap_read(syscon, AM33XX_CTRL_MAC_LO_REG(slave), &macid_lo);
+       regmap_read(syscon, AM33XX_CTRL_MAC_HI_REG(slave), &macid_hi);
+
+       mac_addr[5] = (macid_lo >> 8) & 0xff;
+       mac_addr[4] = macid_lo & 0xff;
+       mac_addr[3] = (macid_hi >> 24) & 0xff;
+       mac_addr[2] = (macid_hi >> 16) & 0xff;
+       mac_addr[1] = (macid_hi >> 8) & 0xff;
+       mac_addr[0] = macid_hi & 0xff;
+
+       return 0;
+}
+
 static int cpsw_probe_dt(struct cpsw_platform_data *data,
                         struct platform_device *pdev)
 {
@@ -1944,15 +2013,23 @@ static int cpsw_probe_dt(struct cpsw_platform_data *data,
                mdio = of_find_device_by_node(mdio_node);
                of_node_put(mdio_node);
                if (!mdio) {
-                       pr_err("Missing mdio platform device\n");
+                       dev_err(&pdev->dev, "Missing mdio platform device\n");
                        return -EINVAL;
                }
                snprintf(slave_data->phy_id, sizeof(slave_data->phy_id),
                         PHY_ID_FMT, mdio->name, phyid);
 
                mac_addr = of_get_mac_address(slave_node);
-               if (mac_addr)
+               if (mac_addr) {
                        memcpy(slave_data->mac_addr, mac_addr, ETH_ALEN);
+               } else {
+                       if (of_machine_is_compatible("ti,am33xx")) {
+                               ret = cpsw_am33xx_cm_get_macid(&pdev->dev, i,
+                                                       slave_data->mac_addr);
+                               if (ret)
+                                       return ret;
+                       }
+               }
 
                slave_data->phy_if = of_get_phy_mode(slave_node);
                if (slave_data->phy_if < 0) {
@@ -2086,6 +2163,7 @@ static int cpsw_probe(struct platform_device *pdev)
        priv->irq_enabled = true;
        if (!priv->cpts) {
                dev_err(&pdev->dev, "error allocating cpts\n");
+               ret = -ENOMEM;
                goto clean_ndev_ret;
        }
 
@@ -2255,18 +2333,24 @@ static int cpsw_probe(struct platform_device *pdev)
        }
 
        while ((res = platform_get_resource(priv->pdev, IORESOURCE_IRQ, k))) {
-               for (i = res->start; i <= res->end; i++) {
-                       if (devm_request_irq(&pdev->dev, i, cpsw_interrupt, 0,
-                                            dev_name(&pdev->dev), priv)) {
-                               dev_err(priv->dev, "error attaching irq\n");
-                               goto clean_ale_ret;
-                       }
-                       priv->irqs_table[k] = i;
-                       priv->num_irqs = k + 1;
+               if (k >= ARRAY_SIZE(priv->irqs_table)) {
+                       ret = -EINVAL;
+                       goto clean_ale_ret;
                }
+
+               ret = devm_request_irq(&pdev->dev, res->start, cpsw_interrupt,
+                                      0, dev_name(&pdev->dev), priv);
+               if (ret < 0) {
+                       dev_err(priv->dev, "error attaching irq (%d)\n", ret);
+                       goto clean_ale_ret;
+               }
+
+               priv->irqs_table[k] = res->start;
                k++;
        }
 
+       priv->num_irqs = k;
+
        ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
 
        ndev->netdev_ops = &cpsw_netdev_ops;
@@ -2308,6 +2392,15 @@ clean_ndev_ret:
        return ret;
 }
 
+static int cpsw_remove_child_device(struct device *dev, void *c)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+
+       of_device_unregister(pdev);
+
+       return 0;
+}
+
 static int cpsw_remove(struct platform_device *pdev)
 {
        struct net_device *ndev = platform_get_drvdata(pdev);
@@ -2322,6 +2415,7 @@ static int cpsw_remove(struct platform_device *pdev)
        cpdma_chan_destroy(priv->rxch);
        cpdma_ctlr_destroy(priv->dma);
        pm_runtime_disable(&pdev->dev);
+       device_for_each_child(&pdev->dev, NULL, cpsw_remove_child_device);
        if (priv->data.dual_emac)
                free_netdev(cpsw_get_slave_ndev(priv, 1));
        free_netdev(ndev);
@@ -2395,7 +2489,6 @@ MODULE_DEVICE_TABLE(of, cpsw_of_mtable);
 static struct platform_driver cpsw_driver = {
        .driver = {
                .name    = "cpsw",
-               .owner   = THIS_MODULE,
                .pm      = &cpsw_pm_ops,
                .of_match_table = cpsw_of_mtable,
        },