| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Applied Micro X-Gene SoC Ethernet v2 Driver |
| * |
| * Copyright (c) 2017, Applied Micro Circuits Corporation |
| * Author(s): Iyappan Subramanian <isubramanian@apm.com> |
| * Keyur Chudgar <kchudgar@apm.com> |
| */ |
| |
| #include "main.h" |
| |
| static int xge_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 data) |
| { |
| struct xge_pdata *pdata = bus->priv; |
| u32 done, val = 0; |
| u8 wait = 10; |
| |
| SET_REG_BITS(&val, PHY_ADDR, phy_id); |
| SET_REG_BITS(&val, REG_ADDR, reg); |
| xge_wr_csr(pdata, MII_MGMT_ADDRESS, val); |
| |
| xge_wr_csr(pdata, MII_MGMT_CONTROL, data); |
| do { |
| usleep_range(5, 10); |
| done = xge_rd_csr(pdata, MII_MGMT_INDICATORS); |
| } while ((done & MII_MGMT_BUSY) && wait--); |
| |
| if (done & MII_MGMT_BUSY) { |
| dev_err(&bus->dev, "MII_MGMT write failed\n"); |
| return -ETIMEDOUT; |
| } |
| |
| return 0; |
| } |
| |
| static int xge_mdio_read(struct mii_bus *bus, int phy_id, int reg) |
| { |
| struct xge_pdata *pdata = bus->priv; |
| u32 data, done, val = 0; |
| u8 wait = 10; |
| |
| SET_REG_BITS(&val, PHY_ADDR, phy_id); |
| SET_REG_BITS(&val, REG_ADDR, reg); |
| xge_wr_csr(pdata, MII_MGMT_ADDRESS, val); |
| |
| xge_wr_csr(pdata, MII_MGMT_COMMAND, MII_READ_CYCLE); |
| do { |
| usleep_range(5, 10); |
| done = xge_rd_csr(pdata, MII_MGMT_INDICATORS); |
| } while ((done & MII_MGMT_BUSY) && wait--); |
| |
| if (done & MII_MGMT_BUSY) { |
| dev_err(&bus->dev, "MII_MGMT read failed\n"); |
| return -ETIMEDOUT; |
| } |
| |
| data = xge_rd_csr(pdata, MII_MGMT_STATUS); |
| xge_wr_csr(pdata, MII_MGMT_COMMAND, 0); |
| |
| return data; |
| } |
| |
| static void xge_adjust_link(struct net_device *ndev) |
| { |
| struct xge_pdata *pdata = netdev_priv(ndev); |
| struct phy_device *phydev = ndev->phydev; |
| |
| if (phydev->link) { |
| if (pdata->phy_speed != phydev->speed) { |
| pdata->phy_speed = phydev->speed; |
| xge_mac_set_speed(pdata); |
| xge_mac_enable(pdata); |
| phy_print_status(phydev); |
| } |
| } else { |
| if (pdata->phy_speed != SPEED_UNKNOWN) { |
| pdata->phy_speed = SPEED_UNKNOWN; |
| xge_mac_disable(pdata); |
| phy_print_status(phydev); |
| } |
| } |
| } |
| |
| void xge_mdio_remove(struct net_device *ndev) |
| { |
| struct xge_pdata *pdata = netdev_priv(ndev); |
| struct mii_bus *mdio_bus = pdata->mdio_bus; |
| |
| if (ndev->phydev) |
| phy_disconnect(ndev->phydev); |
| |
| if (mdio_bus->state == MDIOBUS_REGISTERED) |
| mdiobus_unregister(mdio_bus); |
| |
| mdiobus_free(mdio_bus); |
| } |
| |
| int xge_mdio_config(struct net_device *ndev) |
| { |
| __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; |
| struct xge_pdata *pdata = netdev_priv(ndev); |
| struct device *dev = &pdata->pdev->dev; |
| struct mii_bus *mdio_bus; |
| struct phy_device *phydev; |
| int ret; |
| |
| mdio_bus = mdiobus_alloc(); |
| if (!mdio_bus) |
| return -ENOMEM; |
| |
| mdio_bus->name = "APM X-Gene Ethernet (v2) MDIO Bus"; |
| mdio_bus->read = xge_mdio_read; |
| mdio_bus->write = xge_mdio_write; |
| mdio_bus->priv = pdata; |
| mdio_bus->parent = dev; |
| snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev)); |
| pdata->mdio_bus = mdio_bus; |
| |
| mdio_bus->phy_mask = 0x1; |
| ret = mdiobus_register(mdio_bus); |
| if (ret) |
| goto err; |
| |
| phydev = phy_find_first(mdio_bus); |
| if (!phydev) { |
| dev_err(dev, "no PHY found\n"); |
| ret = -ENODEV; |
| goto err; |
| } |
| phydev = phy_connect(ndev, phydev_name(phydev), |
| &xge_adjust_link, |
| pdata->resources.phy_mode); |
| |
| if (IS_ERR(phydev)) { |
| netdev_err(ndev, "Could not attach to PHY\n"); |
| ret = PTR_ERR(phydev); |
| goto err; |
| } |
| |
| linkmode_set_bit_array(phy_10_100_features_array, |
| ARRAY_SIZE(phy_10_100_features_array), |
| mask); |
| linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, mask); |
| linkmode_set_bit(ETHTOOL_LINK_MODE_AUI_BIT, mask); |
| linkmode_set_bit(ETHTOOL_LINK_MODE_MII_BIT, mask); |
| linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, mask); |
| linkmode_set_bit(ETHTOOL_LINK_MODE_BNC_BIT, mask); |
| |
| linkmode_andnot(phydev->supported, phydev->supported, mask); |
| linkmode_copy(phydev->advertising, phydev->supported); |
| pdata->phy_speed = SPEED_UNKNOWN; |
| |
| return 0; |
| err: |
| xge_mdio_remove(ndev); |
| |
| return ret; |
| } |