ramips: preliminary support for 4.14
[openwrt/staging/kaloz.git] / target / linux / ramips / files-4.14 / drivers / net / ethernet / mtk / mdio.c
diff --git a/target/linux/ramips/files-4.14/drivers/net/ethernet/mtk/mdio.c b/target/linux/ramips/files-4.14/drivers/net/ethernet/mtk/mdio.c
new file mode 100644 (file)
index 0000000..d3798bb
--- /dev/null
@@ -0,0 +1,259 @@
+/*   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; version 2 of the License
+ *
+ *   Copyright (C) 2009-2015 John Crispin <blogic@openwrt.org>
+ *   Copyright (C) 2009-2015 Felix Fietkau <nbd@nbd.name>
+ *   Copyright (C) 2013-2015 Michael Lee <igvtee@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/phy.h>
+#include <linux/of_net.h>
+#include <linux/of_mdio.h>
+
+#include "mtk_eth_soc.h"
+#include "mdio.h"
+
+static int fe_mdio_reset(struct mii_bus *bus)
+{
+       /* TODO */
+       return 0;
+}
+
+static void fe_phy_link_adjust(struct net_device *dev)
+{
+       struct fe_priv *priv = netdev_priv(dev);
+       unsigned long flags;
+       int i;
+
+       spin_lock_irqsave(&priv->phy->lock, flags);
+       for (i = 0; i < 8; i++) {
+               if (priv->phy->phy_node[i]) {
+                       struct phy_device *phydev = priv->phy->phy[i];
+                       int status_change = 0;
+
+                       if (phydev->link)
+                               if (priv->phy->duplex[i] != phydev->duplex ||
+                                   priv->phy->speed[i] != phydev->speed)
+                                       status_change = 1;
+
+                       if (phydev->link != priv->link[i])
+                               status_change = 1;
+
+                       switch (phydev->speed) {
+                       case SPEED_1000:
+                       case SPEED_100:
+                       case SPEED_10:
+                               priv->link[i] = phydev->link;
+                               priv->phy->duplex[i] = phydev->duplex;
+                               priv->phy->speed[i] = phydev->speed;
+
+                               if (status_change &&
+                                   priv->soc->mdio_adjust_link)
+                                       priv->soc->mdio_adjust_link(priv, i);
+                               break;
+                       }
+               }
+       }
+}
+
+int fe_connect_phy_node(struct fe_priv *priv, struct device_node *phy_node)
+{
+       const __be32 *_port = NULL;
+       struct phy_device *phydev;
+       int phy_mode, port;
+
+       _port = of_get_property(phy_node, "reg", NULL);
+
+       if (!_port || (be32_to_cpu(*_port) >= 0x20)) {
+               pr_err("%s: invalid port id\n", phy_node->name);
+               return -EINVAL;
+       }
+       port = be32_to_cpu(*_port);
+       phy_mode = of_get_phy_mode(phy_node);
+       if (phy_mode < 0) {
+               dev_err(priv->device, "incorrect phy-mode %d\n", phy_mode);
+               priv->phy->phy_node[port] = NULL;
+               return -EINVAL;
+       }
+
+       phydev = of_phy_connect(priv->netdev, phy_node, fe_phy_link_adjust,
+                               0, phy_mode);
+       if (IS_ERR(phydev)) {
+               dev_err(priv->device, "could not connect to PHY\n");
+               priv->phy->phy_node[port] = NULL;
+               return PTR_ERR(phydev);
+       }
+
+       phydev->supported &= PHY_GBIT_FEATURES;
+       phydev->advertising = phydev->supported;
+       phydev->no_auto_carrier_off = 1;
+
+       dev_info(priv->device,
+                "connected port %d to PHY at %s [uid=%08x, driver=%s]\n",
+                port, dev_name(&phydev->mdio.dev), phydev->phy_id,
+                phydev->drv->name);
+
+       priv->phy->phy[port] = phydev;
+       priv->link[port] = 0;
+
+       return 0;
+}
+
+static void phy_init(struct fe_priv *priv, struct phy_device *phy)
+{
+       phy_attach(priv->netdev, dev_name(&phy->mdio.dev), PHY_INTERFACE_MODE_MII);
+
+       phy->autoneg = AUTONEG_ENABLE;
+       phy->speed = 0;
+       phy->duplex = 0;
+       phy->supported &= PHY_BASIC_FEATURES;
+       phy->advertising = phy->supported | ADVERTISED_Autoneg;
+
+       phy_start_aneg(phy);
+}
+
+static int fe_phy_connect(struct fe_priv *priv)
+{
+       int i;
+
+       for (i = 0; i < 8; i++) {
+               if (priv->phy->phy_node[i]) {
+                       if (!priv->phy_dev) {
+                               priv->phy_dev = priv->phy->phy[i];
+                               priv->phy_flags = FE_PHY_FLAG_PORT;
+                       }
+               } else if (priv->mii_bus && mdiobus_get_phy(priv->mii_bus, i)) {
+                       phy_init(priv, mdiobus_get_phy(priv->mii_bus, i));
+                       if (!priv->phy_dev) {
+                               priv->phy_dev = mdiobus_get_phy(priv->mii_bus, i);
+                               priv->phy_flags = FE_PHY_FLAG_ATTACH;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+static void fe_phy_disconnect(struct fe_priv *priv)
+{
+       unsigned long flags;
+       int i;
+
+       for (i = 0; i < 8; i++)
+               if (priv->phy->phy_fixed[i]) {
+                       spin_lock_irqsave(&priv->phy->lock, flags);
+                       priv->link[i] = 0;
+                       if (priv->soc->mdio_adjust_link)
+                               priv->soc->mdio_adjust_link(priv, i);
+                       spin_unlock_irqrestore(&priv->phy->lock, flags);
+               } else if (priv->phy->phy[i]) {
+                       phy_disconnect(priv->phy->phy[i]);
+               } else if (priv->mii_bus && mdiobus_get_phy(priv->mii_bus, i)) {
+                       phy_detach(mdiobus_get_phy(priv->mii_bus, i));
+               }
+}
+
+static void fe_phy_start(struct fe_priv *priv)
+{
+       unsigned long flags;
+       int i;
+
+       for (i = 0; i < 8; i++) {
+               if (priv->phy->phy_fixed[i]) {
+                       spin_lock_irqsave(&priv->phy->lock, flags);
+                       priv->link[i] = 1;
+                       if (priv->soc->mdio_adjust_link)
+                               priv->soc->mdio_adjust_link(priv, i);
+                       spin_unlock_irqrestore(&priv->phy->lock, flags);
+               } else if (priv->phy->phy[i]) {
+                       phy_start(priv->phy->phy[i]);
+               }
+       }
+}
+
+static void fe_phy_stop(struct fe_priv *priv)
+{
+       unsigned long flags;
+       int i;
+
+       for (i = 0; i < 8; i++)
+               if (priv->phy->phy_fixed[i]) {
+                       spin_lock_irqsave(&priv->phy->lock, flags);
+                       priv->link[i] = 0;
+                       if (priv->soc->mdio_adjust_link)
+                               priv->soc->mdio_adjust_link(priv, i);
+                       spin_unlock_irqrestore(&priv->phy->lock, flags);
+               } else if (priv->phy->phy[i]) {
+                       phy_stop(priv->phy->phy[i]);
+               }
+}
+
+static struct fe_phy phy_ralink = {
+       .connect = fe_phy_connect,
+       .disconnect = fe_phy_disconnect,
+       .start = fe_phy_start,
+       .stop = fe_phy_stop,
+};
+
+int fe_mdio_init(struct fe_priv *priv)
+{
+       struct device_node *mii_np;
+       int err;
+
+       if (!priv->soc->mdio_read || !priv->soc->mdio_write)
+               return 0;
+
+       spin_lock_init(&phy_ralink.lock);
+       priv->phy = &phy_ralink;
+
+       mii_np = of_get_child_by_name(priv->device->of_node, "mdio-bus");
+       if (!mii_np) {
+               dev_err(priv->device, "no %s child node found", "mdio-bus");
+               return -ENODEV;
+       }
+
+       if (!of_device_is_available(mii_np)) {
+               err = 0;
+               goto err_put_node;
+       }
+
+       priv->mii_bus = mdiobus_alloc();
+       if (!priv->mii_bus) {
+               err = -ENOMEM;
+               goto err_put_node;
+       }
+
+       priv->mii_bus->name = "mdio";
+       priv->mii_bus->read = priv->soc->mdio_read;
+       priv->mii_bus->write = priv->soc->mdio_write;
+       priv->mii_bus->reset = fe_mdio_reset;
+       priv->mii_bus->priv = priv;
+       priv->mii_bus->parent = priv->device;
+
+       snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "%s", mii_np->name);
+       err = of_mdiobus_register(priv->mii_bus, mii_np);
+       if (err)
+               goto err_free_bus;
+
+       return 0;
+
+err_free_bus:
+       kfree(priv->mii_bus);
+err_put_node:
+       of_node_put(mii_np);
+       priv->mii_bus = NULL;
+       return err;
+}
+
+void fe_mdio_cleanup(struct fe_priv *priv)
+{
+       if (!priv->mii_bus)
+               return;
+
+       mdiobus_unregister(priv->mii_bus);
+       of_node_put(priv->mii_bus->dev.of_node);
+       kfree(priv->mii_bus);
+}