162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/**************************************************************************** 362306a36Sopenharmony_ci * Driver for Solarflare network controllers and boards 462306a36Sopenharmony_ci * Copyright 2006-2011 Solarflare Communications Inc. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci/* 762306a36Sopenharmony_ci * Useful functions for working with MDIO clause 45 PHYs 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci#include <linux/types.h> 1062306a36Sopenharmony_ci#include <linux/ethtool.h> 1162306a36Sopenharmony_ci#include <linux/delay.h> 1262306a36Sopenharmony_ci#include "net_driver.h" 1362306a36Sopenharmony_ci#include "mdio_10g.h" 1462306a36Sopenharmony_ci#include "workarounds.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ciunsigned ef4_mdio_id_oui(u32 id) 1762306a36Sopenharmony_ci{ 1862306a36Sopenharmony_ci unsigned oui = 0; 1962306a36Sopenharmony_ci int i; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci /* The bits of the OUI are designated a..x, with a=0 and b variable. 2262306a36Sopenharmony_ci * In the id register c is the MSB but the OUI is conventionally 2362306a36Sopenharmony_ci * written as bytes h..a, p..i, x..q. Reorder the bits accordingly. */ 2462306a36Sopenharmony_ci for (i = 0; i < 22; ++i) 2562306a36Sopenharmony_ci if (id & (1 << (i + 10))) 2662306a36Sopenharmony_ci oui |= 1 << (i ^ 7); 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci return oui; 2962306a36Sopenharmony_ci} 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ciint ef4_mdio_reset_mmd(struct ef4_nic *port, int mmd, 3262306a36Sopenharmony_ci int spins, int spintime) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci u32 ctrl; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci /* Catch callers passing values in the wrong units (or just silly) */ 3762306a36Sopenharmony_ci EF4_BUG_ON_PARANOID(spins * spintime >= 5000); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci ef4_mdio_write(port, mmd, MDIO_CTRL1, MDIO_CTRL1_RESET); 4062306a36Sopenharmony_ci /* Wait for the reset bit to clear. */ 4162306a36Sopenharmony_ci do { 4262306a36Sopenharmony_ci msleep(spintime); 4362306a36Sopenharmony_ci ctrl = ef4_mdio_read(port, mmd, MDIO_CTRL1); 4462306a36Sopenharmony_ci spins--; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci } while (spins && (ctrl & MDIO_CTRL1_RESET)); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci return spins ? spins : -ETIMEDOUT; 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic int ef4_mdio_check_mmd(struct ef4_nic *efx, int mmd) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci int status; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci if (mmd != MDIO_MMD_AN) { 5662306a36Sopenharmony_ci /* Read MMD STATUS2 to check it is responding. */ 5762306a36Sopenharmony_ci status = ef4_mdio_read(efx, mmd, MDIO_STAT2); 5862306a36Sopenharmony_ci if ((status & MDIO_STAT2_DEVPRST) != MDIO_STAT2_DEVPRST_VAL) { 5962306a36Sopenharmony_ci netif_err(efx, hw, efx->net_dev, 6062306a36Sopenharmony_ci "PHY MMD %d not responding.\n", mmd); 6162306a36Sopenharmony_ci return -EIO; 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci } 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci return 0; 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/* This ought to be ridiculous overkill. We expect it to fail rarely */ 6962306a36Sopenharmony_ci#define MDIO45_RESET_TIME 1000 /* ms */ 7062306a36Sopenharmony_ci#define MDIO45_RESET_ITERS 100 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ciint ef4_mdio_wait_reset_mmds(struct ef4_nic *efx, unsigned int mmd_mask) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci const int spintime = MDIO45_RESET_TIME / MDIO45_RESET_ITERS; 7562306a36Sopenharmony_ci int tries = MDIO45_RESET_ITERS; 7662306a36Sopenharmony_ci int rc = 0; 7762306a36Sopenharmony_ci int in_reset; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci while (tries) { 8062306a36Sopenharmony_ci int mask = mmd_mask; 8162306a36Sopenharmony_ci int mmd = 0; 8262306a36Sopenharmony_ci int stat; 8362306a36Sopenharmony_ci in_reset = 0; 8462306a36Sopenharmony_ci while (mask) { 8562306a36Sopenharmony_ci if (mask & 1) { 8662306a36Sopenharmony_ci stat = ef4_mdio_read(efx, mmd, MDIO_CTRL1); 8762306a36Sopenharmony_ci if (stat < 0) { 8862306a36Sopenharmony_ci netif_err(efx, hw, efx->net_dev, 8962306a36Sopenharmony_ci "failed to read status of" 9062306a36Sopenharmony_ci " MMD %d\n", mmd); 9162306a36Sopenharmony_ci return -EIO; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci if (stat & MDIO_CTRL1_RESET) 9462306a36Sopenharmony_ci in_reset |= (1 << mmd); 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci mask = mask >> 1; 9762306a36Sopenharmony_ci mmd++; 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci if (!in_reset) 10062306a36Sopenharmony_ci break; 10162306a36Sopenharmony_ci tries--; 10262306a36Sopenharmony_ci msleep(spintime); 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci if (in_reset != 0) { 10562306a36Sopenharmony_ci netif_err(efx, hw, efx->net_dev, 10662306a36Sopenharmony_ci "not all MMDs came out of reset in time." 10762306a36Sopenharmony_ci " MMDs still in reset: %x\n", in_reset); 10862306a36Sopenharmony_ci rc = -ETIMEDOUT; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci return rc; 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ciint ef4_mdio_check_mmds(struct ef4_nic *efx, unsigned int mmd_mask) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci int mmd = 0, probe_mmd, devs1, devs2; 11662306a36Sopenharmony_ci u32 devices; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci /* Historically we have probed the PHYXS to find out what devices are 11962306a36Sopenharmony_ci * present,but that doesn't work so well if the PHYXS isn't expected 12062306a36Sopenharmony_ci * to exist, if so just find the first item in the list supplied. */ 12162306a36Sopenharmony_ci probe_mmd = (mmd_mask & MDIO_DEVS_PHYXS) ? MDIO_MMD_PHYXS : 12262306a36Sopenharmony_ci __ffs(mmd_mask); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci /* Check all the expected MMDs are present */ 12562306a36Sopenharmony_ci devs1 = ef4_mdio_read(efx, probe_mmd, MDIO_DEVS1); 12662306a36Sopenharmony_ci devs2 = ef4_mdio_read(efx, probe_mmd, MDIO_DEVS2); 12762306a36Sopenharmony_ci if (devs1 < 0 || devs2 < 0) { 12862306a36Sopenharmony_ci netif_err(efx, hw, efx->net_dev, 12962306a36Sopenharmony_ci "failed to read devices present\n"); 13062306a36Sopenharmony_ci return -EIO; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci devices = devs1 | (devs2 << 16); 13362306a36Sopenharmony_ci if ((devices & mmd_mask) != mmd_mask) { 13462306a36Sopenharmony_ci netif_err(efx, hw, efx->net_dev, 13562306a36Sopenharmony_ci "required MMDs not present: got %x, wanted %x\n", 13662306a36Sopenharmony_ci devices, mmd_mask); 13762306a36Sopenharmony_ci return -ENODEV; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci netif_vdbg(efx, hw, efx->net_dev, "Devices present: %x\n", devices); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci /* Check all required MMDs are responding and happy. */ 14262306a36Sopenharmony_ci while (mmd_mask) { 14362306a36Sopenharmony_ci if ((mmd_mask & 1) && ef4_mdio_check_mmd(efx, mmd)) 14462306a36Sopenharmony_ci return -EIO; 14562306a36Sopenharmony_ci mmd_mask = mmd_mask >> 1; 14662306a36Sopenharmony_ci mmd++; 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci return 0; 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cibool ef4_mdio_links_ok(struct ef4_nic *efx, unsigned int mmd_mask) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci /* If the port is in loopback, then we should only consider a subset 15562306a36Sopenharmony_ci * of mmd's */ 15662306a36Sopenharmony_ci if (LOOPBACK_INTERNAL(efx)) 15762306a36Sopenharmony_ci return true; 15862306a36Sopenharmony_ci else if (LOOPBACK_MASK(efx) & LOOPBACKS_WS) 15962306a36Sopenharmony_ci return false; 16062306a36Sopenharmony_ci else if (ef4_phy_mode_disabled(efx->phy_mode)) 16162306a36Sopenharmony_ci return false; 16262306a36Sopenharmony_ci else if (efx->loopback_mode == LOOPBACK_PHYXS) 16362306a36Sopenharmony_ci mmd_mask &= ~(MDIO_DEVS_PHYXS | 16462306a36Sopenharmony_ci MDIO_DEVS_PCS | 16562306a36Sopenharmony_ci MDIO_DEVS_PMAPMD | 16662306a36Sopenharmony_ci MDIO_DEVS_AN); 16762306a36Sopenharmony_ci else if (efx->loopback_mode == LOOPBACK_PCS) 16862306a36Sopenharmony_ci mmd_mask &= ~(MDIO_DEVS_PCS | 16962306a36Sopenharmony_ci MDIO_DEVS_PMAPMD | 17062306a36Sopenharmony_ci MDIO_DEVS_AN); 17162306a36Sopenharmony_ci else if (efx->loopback_mode == LOOPBACK_PMAPMD) 17262306a36Sopenharmony_ci mmd_mask &= ~(MDIO_DEVS_PMAPMD | 17362306a36Sopenharmony_ci MDIO_DEVS_AN); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci return mdio45_links_ok(&efx->mdio, mmd_mask); 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_civoid ef4_mdio_transmit_disable(struct ef4_nic *efx) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci ef4_mdio_set_flag(efx, MDIO_MMD_PMAPMD, 18162306a36Sopenharmony_ci MDIO_PMA_TXDIS, MDIO_PMD_TXDIS_GLOBAL, 18262306a36Sopenharmony_ci efx->phy_mode & PHY_MODE_TX_DISABLED); 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_civoid ef4_mdio_phy_reconfigure(struct ef4_nic *efx) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci ef4_mdio_set_flag(efx, MDIO_MMD_PMAPMD, 18862306a36Sopenharmony_ci MDIO_CTRL1, MDIO_PMA_CTRL1_LOOPBACK, 18962306a36Sopenharmony_ci efx->loopback_mode == LOOPBACK_PMAPMD); 19062306a36Sopenharmony_ci ef4_mdio_set_flag(efx, MDIO_MMD_PCS, 19162306a36Sopenharmony_ci MDIO_CTRL1, MDIO_PCS_CTRL1_LOOPBACK, 19262306a36Sopenharmony_ci efx->loopback_mode == LOOPBACK_PCS); 19362306a36Sopenharmony_ci ef4_mdio_set_flag(efx, MDIO_MMD_PHYXS, 19462306a36Sopenharmony_ci MDIO_CTRL1, MDIO_PHYXS_CTRL1_LOOPBACK, 19562306a36Sopenharmony_ci efx->loopback_mode == LOOPBACK_PHYXS_WS); 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic void ef4_mdio_set_mmd_lpower(struct ef4_nic *efx, 19962306a36Sopenharmony_ci int lpower, int mmd) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci int stat = ef4_mdio_read(efx, mmd, MDIO_STAT1); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci netif_vdbg(efx, drv, efx->net_dev, "Setting low power mode for MMD %d to %d\n", 20462306a36Sopenharmony_ci mmd, lpower); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci if (stat & MDIO_STAT1_LPOWERABLE) { 20762306a36Sopenharmony_ci ef4_mdio_set_flag(efx, mmd, MDIO_CTRL1, 20862306a36Sopenharmony_ci MDIO_CTRL1_LPOWER, lpower); 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_civoid ef4_mdio_set_mmds_lpower(struct ef4_nic *efx, 21362306a36Sopenharmony_ci int low_power, unsigned int mmd_mask) 21462306a36Sopenharmony_ci{ 21562306a36Sopenharmony_ci int mmd = 0; 21662306a36Sopenharmony_ci mmd_mask &= ~MDIO_DEVS_AN; 21762306a36Sopenharmony_ci while (mmd_mask) { 21862306a36Sopenharmony_ci if (mmd_mask & 1) 21962306a36Sopenharmony_ci ef4_mdio_set_mmd_lpower(efx, low_power, mmd); 22062306a36Sopenharmony_ci mmd_mask = (mmd_mask >> 1); 22162306a36Sopenharmony_ci mmd++; 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci/** 22662306a36Sopenharmony_ci * ef4_mdio_set_link_ksettings - Set (some of) the PHY settings over MDIO. 22762306a36Sopenharmony_ci * @efx: Efx NIC 22862306a36Sopenharmony_ci * @cmd: New settings 22962306a36Sopenharmony_ci */ 23062306a36Sopenharmony_ciint ef4_mdio_set_link_ksettings(struct ef4_nic *efx, 23162306a36Sopenharmony_ci const struct ethtool_link_ksettings *cmd) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci struct ethtool_link_ksettings prev = { 23462306a36Sopenharmony_ci .base.cmd = ETHTOOL_GLINKSETTINGS 23562306a36Sopenharmony_ci }; 23662306a36Sopenharmony_ci u32 prev_advertising, advertising; 23762306a36Sopenharmony_ci u32 prev_supported; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci efx->phy_op->get_link_ksettings(efx, &prev); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci ethtool_convert_link_mode_to_legacy_u32(&advertising, 24262306a36Sopenharmony_ci cmd->link_modes.advertising); 24362306a36Sopenharmony_ci ethtool_convert_link_mode_to_legacy_u32(&prev_advertising, 24462306a36Sopenharmony_ci prev.link_modes.advertising); 24562306a36Sopenharmony_ci ethtool_convert_link_mode_to_legacy_u32(&prev_supported, 24662306a36Sopenharmony_ci prev.link_modes.supported); 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci if (advertising == prev_advertising && 24962306a36Sopenharmony_ci cmd->base.speed == prev.base.speed && 25062306a36Sopenharmony_ci cmd->base.duplex == prev.base.duplex && 25162306a36Sopenharmony_ci cmd->base.port == prev.base.port && 25262306a36Sopenharmony_ci cmd->base.autoneg == prev.base.autoneg) 25362306a36Sopenharmony_ci return 0; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci /* We can only change these settings for -T PHYs */ 25662306a36Sopenharmony_ci if (prev.base.port != PORT_TP || cmd->base.port != PORT_TP) 25762306a36Sopenharmony_ci return -EINVAL; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci /* Check that PHY supports these settings */ 26062306a36Sopenharmony_ci if (!cmd->base.autoneg || 26162306a36Sopenharmony_ci (advertising | SUPPORTED_Autoneg) & ~prev_supported) 26262306a36Sopenharmony_ci return -EINVAL; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci ef4_link_set_advertising(efx, advertising | ADVERTISED_Autoneg); 26562306a36Sopenharmony_ci ef4_mdio_an_reconfigure(efx); 26662306a36Sopenharmony_ci return 0; 26762306a36Sopenharmony_ci} 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci/** 27062306a36Sopenharmony_ci * ef4_mdio_an_reconfigure - Push advertising flags and restart autonegotiation 27162306a36Sopenharmony_ci * @efx: Efx NIC 27262306a36Sopenharmony_ci */ 27362306a36Sopenharmony_civoid ef4_mdio_an_reconfigure(struct ef4_nic *efx) 27462306a36Sopenharmony_ci{ 27562306a36Sopenharmony_ci int reg; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci WARN_ON(!(efx->mdio.mmds & MDIO_DEVS_AN)); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci /* Set up the base page */ 28062306a36Sopenharmony_ci reg = ADVERTISE_CSMA | ADVERTISE_RESV; 28162306a36Sopenharmony_ci if (efx->link_advertising & ADVERTISED_Pause) 28262306a36Sopenharmony_ci reg |= ADVERTISE_PAUSE_CAP; 28362306a36Sopenharmony_ci if (efx->link_advertising & ADVERTISED_Asym_Pause) 28462306a36Sopenharmony_ci reg |= ADVERTISE_PAUSE_ASYM; 28562306a36Sopenharmony_ci ef4_mdio_write(efx, MDIO_MMD_AN, MDIO_AN_ADVERTISE, reg); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci /* Set up the (extended) next page */ 28862306a36Sopenharmony_ci efx->phy_op->set_npage_adv(efx, efx->link_advertising); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci /* Enable and restart AN */ 29162306a36Sopenharmony_ci reg = ef4_mdio_read(efx, MDIO_MMD_AN, MDIO_CTRL1); 29262306a36Sopenharmony_ci reg |= MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART | MDIO_AN_CTRL1_XNP; 29362306a36Sopenharmony_ci ef4_mdio_write(efx, MDIO_MMD_AN, MDIO_CTRL1, reg); 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ciu8 ef4_mdio_get_pause(struct ef4_nic *efx) 29762306a36Sopenharmony_ci{ 29862306a36Sopenharmony_ci BUILD_BUG_ON(EF4_FC_AUTO & (EF4_FC_RX | EF4_FC_TX)); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci if (!(efx->wanted_fc & EF4_FC_AUTO)) 30162306a36Sopenharmony_ci return efx->wanted_fc; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci WARN_ON(!(efx->mdio.mmds & MDIO_DEVS_AN)); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci return mii_resolve_flowctrl_fdx( 30662306a36Sopenharmony_ci mii_advertise_flowctrl(efx->wanted_fc), 30762306a36Sopenharmony_ci ef4_mdio_read(efx, MDIO_MMD_AN, MDIO_AN_LPA)); 30862306a36Sopenharmony_ci} 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ciint ef4_mdio_test_alive(struct ef4_nic *efx) 31162306a36Sopenharmony_ci{ 31262306a36Sopenharmony_ci int rc; 31362306a36Sopenharmony_ci int devad = __ffs(efx->mdio.mmds); 31462306a36Sopenharmony_ci u16 physid1, physid2; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci mutex_lock(&efx->mac_lock); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci physid1 = ef4_mdio_read(efx, devad, MDIO_DEVID1); 31962306a36Sopenharmony_ci physid2 = ef4_mdio_read(efx, devad, MDIO_DEVID2); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci if ((physid1 == 0x0000) || (physid1 == 0xffff) || 32262306a36Sopenharmony_ci (physid2 == 0x0000) || (physid2 == 0xffff)) { 32362306a36Sopenharmony_ci netif_err(efx, hw, efx->net_dev, 32462306a36Sopenharmony_ci "no MDIO PHY present with ID %d\n", efx->mdio.prtad); 32562306a36Sopenharmony_ci rc = -EINVAL; 32662306a36Sopenharmony_ci } else { 32762306a36Sopenharmony_ci rc = ef4_mdio_check_mmds(efx, efx->mdio.mmds); 32862306a36Sopenharmony_ci } 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci mutex_unlock(&efx->mac_lock); 33162306a36Sopenharmony_ci return rc; 33262306a36Sopenharmony_ci} 333