| /* 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-2016 John Crispin <blogic@openwrt.org> |
| * Copyright (C) 2009-2016 Felix Fietkau <nbd@openwrt.org> |
| * Copyright (C) 2013-2016 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 mtk_mdio_reset(struct mii_bus *bus) |
| { |
| /* TODO */ |
| return 0; |
| } |
| |
| static void mtk_phy_link_adjust(struct net_device *dev) |
| { |
| struct mtk_eth *eth = netdev_priv(dev); |
| unsigned long flags; |
| int i; |
| |
| spin_lock_irqsave(ð->phy->lock, flags); |
| for (i = 0; i < 8; i++) { |
| if (eth->phy->phy_node[i]) { |
| struct phy_device *phydev = eth->phy->phy[i]; |
| int status_change = 0; |
| |
| if (phydev->link) |
| if (eth->phy->duplex[i] != phydev->duplex || |
| eth->phy->speed[i] != phydev->speed) |
| status_change = 1; |
| |
| if (phydev->link != eth->link[i]) |
| status_change = 1; |
| |
| switch (phydev->speed) { |
| case SPEED_1000: |
| case SPEED_100: |
| case SPEED_10: |
| eth->link[i] = phydev->link; |
| eth->phy->duplex[i] = phydev->duplex; |
| eth->phy->speed[i] = phydev->speed; |
| |
| if (status_change && |
| eth->soc->mdio_adjust_link) |
| eth->soc->mdio_adjust_link(eth, i); |
| break; |
| } |
| } |
| } |
| } |
| |
| int mtk_connect_phy_node(struct mtk_eth *eth, struct mtk_mac *mac, |
| 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(eth->dev, "incorrect phy-mode %d\n", phy_mode); |
| eth->phy->phy_node[port] = NULL; |
| return -EINVAL; |
| } |
| |
| phydev = of_phy_connect(eth->netdev[mac->id], phy_node, |
| mtk_phy_link_adjust, 0, phy_mode); |
| if (!phydev) { |
| dev_err(eth->dev, "could not connect to PHY\n"); |
| eth->phy->phy_node[port] = NULL; |
| return -ENODEV; |
| } |
| |
| phydev->supported &= PHY_GBIT_FEATURES; |
| phydev->advertising = phydev->supported; |
| |
| dev_info(eth->dev, |
| "connected port %d to PHY at %s [uid=%08x, driver=%s]\n", |
| port, phydev_name(phydev), phydev->phy_id, |
| phydev->drv->name); |
| |
| eth->phy->phy[port] = phydev; |
| eth->link[port] = 0; |
| |
| return 0; |
| } |
| |
| static void phy_init(struct mtk_eth *eth, struct mtk_mac *mac, |
| struct phy_device *phy) |
| { |
| phy_attach(eth->netdev[mac->id], phydev_name(phy), |
| 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 mtk_phy_connect(struct mtk_mac *mac) |
| { |
| struct mtk_eth *eth = mac->hw; |
| int i; |
| |
| for (i = 0; i < 8; i++) { |
| if (eth->phy->phy_node[i]) { |
| if (!mac->phy_dev) { |
| mac->phy_dev = eth->phy->phy[i]; |
| mac->phy_flags = MTK_PHY_FLAG_PORT; |
| } |
| } else if (eth->mii_bus) { |
| struct phy_device *phy; |
| phy = mdiobus_get_phy(eth->mii_bus, i); |
| if (phy) { |
| phy_init(eth, mac, phy); |
| if (!mac->phy_dev) { |
| mac->phy_dev = phy; |
| mac->phy_flags = MTK_PHY_FLAG_ATTACH; |
| } |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void mtk_phy_disconnect(struct mtk_mac *mac) |
| { |
| struct mtk_eth *eth = mac->hw; |
| unsigned long flags; |
| int i; |
| |
| for (i = 0; i < 8; i++) |
| if (eth->phy->phy_fixed[i]) { |
| spin_lock_irqsave(ð->phy->lock, flags); |
| eth->link[i] = 0; |
| if (eth->soc->mdio_adjust_link) |
| eth->soc->mdio_adjust_link(eth, i); |
| spin_unlock_irqrestore(ð->phy->lock, flags); |
| } else if (eth->phy->phy[i]) { |
| phy_disconnect(eth->phy->phy[i]); |
| } else if (eth->mii_bus) { |
| struct phy_device *phy = mdiobus_get_phy(eth->mii_bus, i); |
| if (phy) |
| phy_detach(phy); |
| } |
| } |
| |
| static void mtk_phy_start(struct mtk_mac *mac) |
| { |
| struct mtk_eth *eth = mac->hw; |
| unsigned long flags; |
| int i; |
| |
| for (i = 0; i < 8; i++) { |
| if (eth->phy->phy_fixed[i]) { |
| spin_lock_irqsave(ð->phy->lock, flags); |
| eth->link[i] = 1; |
| if (eth->soc->mdio_adjust_link) |
| eth->soc->mdio_adjust_link(eth, i); |
| spin_unlock_irqrestore(ð->phy->lock, flags); |
| } else if (eth->phy->phy[i]) { |
| phy_start(eth->phy->phy[i]); |
| } |
| } |
| } |
| |
| static void mtk_phy_stop(struct mtk_mac *mac) |
| { |
| struct mtk_eth *eth = mac->hw; |
| unsigned long flags; |
| int i; |
| |
| for (i = 0; i < 8; i++) |
| if (eth->phy->phy_fixed[i]) { |
| spin_lock_irqsave(ð->phy->lock, flags); |
| eth->link[i] = 0; |
| if (eth->soc->mdio_adjust_link) |
| eth->soc->mdio_adjust_link(eth, i); |
| spin_unlock_irqrestore(ð->phy->lock, flags); |
| } else if (eth->phy->phy[i]) { |
| phy_stop(eth->phy->phy[i]); |
| } |
| } |
| |
| static struct mtk_phy phy_ralink = { |
| .connect = mtk_phy_connect, |
| .disconnect = mtk_phy_disconnect, |
| .start = mtk_phy_start, |
| .stop = mtk_phy_stop, |
| }; |
| |
| int mtk_mdio_init(struct mtk_eth *eth) |
| { |
| struct device_node *mii_np; |
| int err; |
| |
| if (!eth->soc->mdio_read || !eth->soc->mdio_write) |
| return 0; |
| |
| spin_lock_init(&phy_ralink.lock); |
| eth->phy = &phy_ralink; |
| |
| mii_np = of_get_child_by_name(eth->dev->of_node, "mdio-bus"); |
| if (!mii_np) { |
| dev_err(eth->dev, "no %s child node found", "mdio-bus"); |
| return -ENODEV; |
| } |
| |
| if (!of_device_is_available(mii_np)) { |
| err = 0; |
| goto err_put_node; |
| } |
| |
| eth->mii_bus = mdiobus_alloc(); |
| if (!eth->mii_bus) { |
| err = -ENOMEM; |
| goto err_put_node; |
| } |
| |
| eth->mii_bus->name = "mdio"; |
| eth->mii_bus->read = eth->soc->mdio_read; |
| eth->mii_bus->write = eth->soc->mdio_write; |
| eth->mii_bus->reset = mtk_mdio_reset; |
| eth->mii_bus->priv = eth; |
| eth->mii_bus->parent = eth->dev; |
| |
| snprintf(eth->mii_bus->id, MII_BUS_ID_SIZE, "%s", mii_np->name); |
| err = of_mdiobus_register(eth->mii_bus, mii_np); |
| if (err) |
| goto err_free_bus; |
| |
| return 0; |
| |
| err_free_bus: |
| kfree(eth->mii_bus); |
| err_put_node: |
| of_node_put(mii_np); |
| eth->mii_bus = NULL; |
| return err; |
| } |
| |
| void mtk_mdio_cleanup(struct mtk_eth *eth) |
| { |
| if (!eth->mii_bus) |
| return; |
| |
| mdiobus_unregister(eth->mii_bus); |
| of_node_put(eth->mii_bus->dev.of_node); |
| kfree(eth->mii_bus); |
| } |