162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci mii.c: MII interface library 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci Maintained by Jeff Garzik <jgarzik@pobox.com> 662306a36Sopenharmony_ci Copyright 2001,2002 Jeff Garzik 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci Various code came from myson803.c and other files by 962306a36Sopenharmony_ci Donald Becker. Copyright: 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci Written 1998-2002 by Donald Becker. 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci This software may be used and distributed according 1462306a36Sopenharmony_ci to the terms of the GNU General Public License (GPL), 1562306a36Sopenharmony_ci incorporated herein by reference. Drivers based on 1662306a36Sopenharmony_ci or derived from this code fall under the GPL and must 1762306a36Sopenharmony_ci retain the authorship, copyright and license notice. 1862306a36Sopenharmony_ci This file is not a complete program and may only be 1962306a36Sopenharmony_ci used when the entire operating system is licensed 2062306a36Sopenharmony_ci under the GPL. 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci The author may be reached as becker@scyld.com, or C/O 2362306a36Sopenharmony_ci Scyld Computing Corporation 2462306a36Sopenharmony_ci 410 Severn Ave., Suite 210 2562306a36Sopenharmony_ci Annapolis MD 21403 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#include <linux/kernel.h> 3162306a36Sopenharmony_ci#include <linux/module.h> 3262306a36Sopenharmony_ci#include <linux/netdevice.h> 3362306a36Sopenharmony_ci#include <linux/ethtool.h> 3462306a36Sopenharmony_ci#include <linux/mii.h> 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistatic u32 mii_get_an(struct mii_if_info *mii, u16 addr) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci int advert; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci advert = mii->mdio_read(mii->dev, mii->phy_id, addr); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci return mii_lpa_to_ethtool_lpa_t(advert); 4362306a36Sopenharmony_ci} 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci/** 4662306a36Sopenharmony_ci * mii_ethtool_gset - get settings that are specified in @ecmd 4762306a36Sopenharmony_ci * @mii: MII interface 4862306a36Sopenharmony_ci * @ecmd: requested ethtool_cmd 4962306a36Sopenharmony_ci * 5062306a36Sopenharmony_ci * The @ecmd parameter is expected to have been cleared before calling 5162306a36Sopenharmony_ci * mii_ethtool_gset(). 5262306a36Sopenharmony_ci */ 5362306a36Sopenharmony_civoid mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci struct net_device *dev = mii->dev; 5662306a36Sopenharmony_ci u16 bmcr, bmsr, ctrl1000 = 0, stat1000 = 0; 5762306a36Sopenharmony_ci u32 nego; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci ecmd->supported = 6062306a36Sopenharmony_ci (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full | 6162306a36Sopenharmony_ci SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full | 6262306a36Sopenharmony_ci SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII); 6362306a36Sopenharmony_ci if (mii->supports_gmii) 6462306a36Sopenharmony_ci ecmd->supported |= SUPPORTED_1000baseT_Half | 6562306a36Sopenharmony_ci SUPPORTED_1000baseT_Full; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci /* only supports twisted-pair */ 6862306a36Sopenharmony_ci ecmd->port = PORT_MII; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci /* only supports internal transceiver */ 7162306a36Sopenharmony_ci ecmd->transceiver = XCVR_INTERNAL; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci /* this isn't fully supported at higher layers */ 7462306a36Sopenharmony_ci ecmd->phy_address = mii->phy_id; 7562306a36Sopenharmony_ci ecmd->mdio_support = ETH_MDIO_SUPPORTS_C22; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci ecmd->advertising = ADVERTISED_TP | ADVERTISED_MII; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR); 8062306a36Sopenharmony_ci bmsr = mii->mdio_read(dev, mii->phy_id, MII_BMSR); 8162306a36Sopenharmony_ci if (mii->supports_gmii) { 8262306a36Sopenharmony_ci ctrl1000 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000); 8362306a36Sopenharmony_ci stat1000 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000); 8462306a36Sopenharmony_ci } 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci ecmd->advertising |= mii_get_an(mii, MII_ADVERTISE); 8762306a36Sopenharmony_ci if (mii->supports_gmii) 8862306a36Sopenharmony_ci ecmd->advertising |= 8962306a36Sopenharmony_ci mii_ctrl1000_to_ethtool_adv_t(ctrl1000); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci if (bmcr & BMCR_ANENABLE) { 9262306a36Sopenharmony_ci ecmd->advertising |= ADVERTISED_Autoneg; 9362306a36Sopenharmony_ci ecmd->autoneg = AUTONEG_ENABLE; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci if (bmsr & BMSR_ANEGCOMPLETE) { 9662306a36Sopenharmony_ci ecmd->lp_advertising = mii_get_an(mii, MII_LPA); 9762306a36Sopenharmony_ci ecmd->lp_advertising |= 9862306a36Sopenharmony_ci mii_stat1000_to_ethtool_lpa_t(stat1000); 9962306a36Sopenharmony_ci } else { 10062306a36Sopenharmony_ci ecmd->lp_advertising = 0; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci nego = ecmd->advertising & ecmd->lp_advertising; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (nego & (ADVERTISED_1000baseT_Full | 10662306a36Sopenharmony_ci ADVERTISED_1000baseT_Half)) { 10762306a36Sopenharmony_ci ethtool_cmd_speed_set(ecmd, SPEED_1000); 10862306a36Sopenharmony_ci ecmd->duplex = !!(nego & ADVERTISED_1000baseT_Full); 10962306a36Sopenharmony_ci } else if (nego & (ADVERTISED_100baseT_Full | 11062306a36Sopenharmony_ci ADVERTISED_100baseT_Half)) { 11162306a36Sopenharmony_ci ethtool_cmd_speed_set(ecmd, SPEED_100); 11262306a36Sopenharmony_ci ecmd->duplex = !!(nego & ADVERTISED_100baseT_Full); 11362306a36Sopenharmony_ci } else { 11462306a36Sopenharmony_ci ethtool_cmd_speed_set(ecmd, SPEED_10); 11562306a36Sopenharmony_ci ecmd->duplex = !!(nego & ADVERTISED_10baseT_Full); 11662306a36Sopenharmony_ci } 11762306a36Sopenharmony_ci } else { 11862306a36Sopenharmony_ci ecmd->autoneg = AUTONEG_DISABLE; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci ethtool_cmd_speed_set(ecmd, 12162306a36Sopenharmony_ci ((bmcr & BMCR_SPEED1000 && 12262306a36Sopenharmony_ci (bmcr & BMCR_SPEED100) == 0) ? 12362306a36Sopenharmony_ci SPEED_1000 : 12462306a36Sopenharmony_ci ((bmcr & BMCR_SPEED100) ? 12562306a36Sopenharmony_ci SPEED_100 : SPEED_10))); 12662306a36Sopenharmony_ci ecmd->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF; 12762306a36Sopenharmony_ci } 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci mii->full_duplex = ecmd->duplex; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci /* ignore maxtxpkt, maxrxpkt for now */ 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci/** 13562306a36Sopenharmony_ci * mii_ethtool_get_link_ksettings - get settings that are specified in @cmd 13662306a36Sopenharmony_ci * @mii: MII interface 13762306a36Sopenharmony_ci * @cmd: requested ethtool_link_ksettings 13862306a36Sopenharmony_ci * 13962306a36Sopenharmony_ci * The @cmd parameter is expected to have been cleared before calling 14062306a36Sopenharmony_ci * mii_ethtool_get_link_ksettings(). 14162306a36Sopenharmony_ci */ 14262306a36Sopenharmony_civoid mii_ethtool_get_link_ksettings(struct mii_if_info *mii, 14362306a36Sopenharmony_ci struct ethtool_link_ksettings *cmd) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci struct net_device *dev = mii->dev; 14662306a36Sopenharmony_ci u16 bmcr, bmsr, ctrl1000 = 0, stat1000 = 0; 14762306a36Sopenharmony_ci u32 nego, supported, advertising, lp_advertising; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci supported = (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full | 15062306a36Sopenharmony_ci SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full | 15162306a36Sopenharmony_ci SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII); 15262306a36Sopenharmony_ci if (mii->supports_gmii) 15362306a36Sopenharmony_ci supported |= SUPPORTED_1000baseT_Half | 15462306a36Sopenharmony_ci SUPPORTED_1000baseT_Full; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci /* only supports twisted-pair */ 15762306a36Sopenharmony_ci cmd->base.port = PORT_MII; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci /* this isn't fully supported at higher layers */ 16062306a36Sopenharmony_ci cmd->base.phy_address = mii->phy_id; 16162306a36Sopenharmony_ci cmd->base.mdio_support = ETH_MDIO_SUPPORTS_C22; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci advertising = ADVERTISED_TP | ADVERTISED_MII; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR); 16662306a36Sopenharmony_ci bmsr = mii->mdio_read(dev, mii->phy_id, MII_BMSR); 16762306a36Sopenharmony_ci if (mii->supports_gmii) { 16862306a36Sopenharmony_ci ctrl1000 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000); 16962306a36Sopenharmony_ci stat1000 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000); 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci advertising |= mii_get_an(mii, MII_ADVERTISE); 17362306a36Sopenharmony_ci if (mii->supports_gmii) 17462306a36Sopenharmony_ci advertising |= mii_ctrl1000_to_ethtool_adv_t(ctrl1000); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci if (bmcr & BMCR_ANENABLE) { 17762306a36Sopenharmony_ci advertising |= ADVERTISED_Autoneg; 17862306a36Sopenharmony_ci cmd->base.autoneg = AUTONEG_ENABLE; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci if (bmsr & BMSR_ANEGCOMPLETE) { 18162306a36Sopenharmony_ci lp_advertising = mii_get_an(mii, MII_LPA); 18262306a36Sopenharmony_ci lp_advertising |= 18362306a36Sopenharmony_ci mii_stat1000_to_ethtool_lpa_t(stat1000); 18462306a36Sopenharmony_ci } else { 18562306a36Sopenharmony_ci lp_advertising = 0; 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci nego = advertising & lp_advertising; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci if (nego & (ADVERTISED_1000baseT_Full | 19162306a36Sopenharmony_ci ADVERTISED_1000baseT_Half)) { 19262306a36Sopenharmony_ci cmd->base.speed = SPEED_1000; 19362306a36Sopenharmony_ci cmd->base.duplex = !!(nego & ADVERTISED_1000baseT_Full); 19462306a36Sopenharmony_ci } else if (nego & (ADVERTISED_100baseT_Full | 19562306a36Sopenharmony_ci ADVERTISED_100baseT_Half)) { 19662306a36Sopenharmony_ci cmd->base.speed = SPEED_100; 19762306a36Sopenharmony_ci cmd->base.duplex = !!(nego & ADVERTISED_100baseT_Full); 19862306a36Sopenharmony_ci } else { 19962306a36Sopenharmony_ci cmd->base.speed = SPEED_10; 20062306a36Sopenharmony_ci cmd->base.duplex = !!(nego & ADVERTISED_10baseT_Full); 20162306a36Sopenharmony_ci } 20262306a36Sopenharmony_ci } else { 20362306a36Sopenharmony_ci cmd->base.autoneg = AUTONEG_DISABLE; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci cmd->base.speed = ((bmcr & BMCR_SPEED1000 && 20662306a36Sopenharmony_ci (bmcr & BMCR_SPEED100) == 0) ? 20762306a36Sopenharmony_ci SPEED_1000 : 20862306a36Sopenharmony_ci ((bmcr & BMCR_SPEED100) ? 20962306a36Sopenharmony_ci SPEED_100 : SPEED_10)); 21062306a36Sopenharmony_ci cmd->base.duplex = (bmcr & BMCR_FULLDPLX) ? 21162306a36Sopenharmony_ci DUPLEX_FULL : DUPLEX_HALF; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci lp_advertising = 0; 21462306a36Sopenharmony_ci } 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci mii->full_duplex = cmd->base.duplex; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported, 21962306a36Sopenharmony_ci supported); 22062306a36Sopenharmony_ci ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.advertising, 22162306a36Sopenharmony_ci advertising); 22262306a36Sopenharmony_ci ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.lp_advertising, 22362306a36Sopenharmony_ci lp_advertising); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci /* ignore maxtxpkt, maxrxpkt for now */ 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci/** 22962306a36Sopenharmony_ci * mii_ethtool_sset - set settings that are specified in @ecmd 23062306a36Sopenharmony_ci * @mii: MII interface 23162306a36Sopenharmony_ci * @ecmd: requested ethtool_cmd 23262306a36Sopenharmony_ci * 23362306a36Sopenharmony_ci * Returns 0 for success, negative on error. 23462306a36Sopenharmony_ci */ 23562306a36Sopenharmony_ciint mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci struct net_device *dev = mii->dev; 23862306a36Sopenharmony_ci u32 speed = ethtool_cmd_speed(ecmd); 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci if (speed != SPEED_10 && 24162306a36Sopenharmony_ci speed != SPEED_100 && 24262306a36Sopenharmony_ci speed != SPEED_1000) 24362306a36Sopenharmony_ci return -EINVAL; 24462306a36Sopenharmony_ci if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL) 24562306a36Sopenharmony_ci return -EINVAL; 24662306a36Sopenharmony_ci if (ecmd->port != PORT_MII) 24762306a36Sopenharmony_ci return -EINVAL; 24862306a36Sopenharmony_ci if (ecmd->transceiver != XCVR_INTERNAL) 24962306a36Sopenharmony_ci return -EINVAL; 25062306a36Sopenharmony_ci if (ecmd->phy_address != mii->phy_id) 25162306a36Sopenharmony_ci return -EINVAL; 25262306a36Sopenharmony_ci if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE) 25362306a36Sopenharmony_ci return -EINVAL; 25462306a36Sopenharmony_ci if ((speed == SPEED_1000) && (!mii->supports_gmii)) 25562306a36Sopenharmony_ci return -EINVAL; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci /* ignore supported, maxtxpkt, maxrxpkt */ 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci if (ecmd->autoneg == AUTONEG_ENABLE) { 26062306a36Sopenharmony_ci u32 bmcr, advert, tmp; 26162306a36Sopenharmony_ci u32 advert2 = 0, tmp2 = 0; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci if ((ecmd->advertising & (ADVERTISED_10baseT_Half | 26462306a36Sopenharmony_ci ADVERTISED_10baseT_Full | 26562306a36Sopenharmony_ci ADVERTISED_100baseT_Half | 26662306a36Sopenharmony_ci ADVERTISED_100baseT_Full | 26762306a36Sopenharmony_ci ADVERTISED_1000baseT_Half | 26862306a36Sopenharmony_ci ADVERTISED_1000baseT_Full)) == 0) 26962306a36Sopenharmony_ci return -EINVAL; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci /* advertise only what has been requested */ 27262306a36Sopenharmony_ci advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE); 27362306a36Sopenharmony_ci tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4); 27462306a36Sopenharmony_ci if (mii->supports_gmii) { 27562306a36Sopenharmony_ci advert2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000); 27662306a36Sopenharmony_ci tmp2 = advert2 & ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL); 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci tmp |= ethtool_adv_to_mii_adv_t(ecmd->advertising); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci if (mii->supports_gmii) 28162306a36Sopenharmony_ci tmp2 |= 28262306a36Sopenharmony_ci ethtool_adv_to_mii_ctrl1000_t(ecmd->advertising); 28362306a36Sopenharmony_ci if (advert != tmp) { 28462306a36Sopenharmony_ci mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp); 28562306a36Sopenharmony_ci mii->advertising = tmp; 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci if ((mii->supports_gmii) && (advert2 != tmp2)) 28862306a36Sopenharmony_ci mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci /* turn on autonegotiation, and force a renegotiate */ 29162306a36Sopenharmony_ci bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR); 29262306a36Sopenharmony_ci bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART); 29362306a36Sopenharmony_ci mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr); 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci mii->force_media = 0; 29662306a36Sopenharmony_ci } else { 29762306a36Sopenharmony_ci u32 bmcr, tmp; 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci /* turn off auto negotiation, set speed and duplexity */ 30062306a36Sopenharmony_ci bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR); 30162306a36Sopenharmony_ci tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 | 30262306a36Sopenharmony_ci BMCR_SPEED1000 | BMCR_FULLDPLX); 30362306a36Sopenharmony_ci if (speed == SPEED_1000) 30462306a36Sopenharmony_ci tmp |= BMCR_SPEED1000; 30562306a36Sopenharmony_ci else if (speed == SPEED_100) 30662306a36Sopenharmony_ci tmp |= BMCR_SPEED100; 30762306a36Sopenharmony_ci if (ecmd->duplex == DUPLEX_FULL) { 30862306a36Sopenharmony_ci tmp |= BMCR_FULLDPLX; 30962306a36Sopenharmony_ci mii->full_duplex = 1; 31062306a36Sopenharmony_ci } else 31162306a36Sopenharmony_ci mii->full_duplex = 0; 31262306a36Sopenharmony_ci if (bmcr != tmp) 31362306a36Sopenharmony_ci mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci mii->force_media = 1; 31662306a36Sopenharmony_ci } 31762306a36Sopenharmony_ci return 0; 31862306a36Sopenharmony_ci} 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci/** 32162306a36Sopenharmony_ci * mii_ethtool_set_link_ksettings - set settings that are specified in @cmd 32262306a36Sopenharmony_ci * @mii: MII interfaces 32362306a36Sopenharmony_ci * @cmd: requested ethtool_link_ksettings 32462306a36Sopenharmony_ci * 32562306a36Sopenharmony_ci * Returns 0 for success, negative on error. 32662306a36Sopenharmony_ci */ 32762306a36Sopenharmony_ciint mii_ethtool_set_link_ksettings(struct mii_if_info *mii, 32862306a36Sopenharmony_ci const struct ethtool_link_ksettings *cmd) 32962306a36Sopenharmony_ci{ 33062306a36Sopenharmony_ci struct net_device *dev = mii->dev; 33162306a36Sopenharmony_ci u32 speed = cmd->base.speed; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci if (speed != SPEED_10 && 33462306a36Sopenharmony_ci speed != SPEED_100 && 33562306a36Sopenharmony_ci speed != SPEED_1000) 33662306a36Sopenharmony_ci return -EINVAL; 33762306a36Sopenharmony_ci if (cmd->base.duplex != DUPLEX_HALF && cmd->base.duplex != DUPLEX_FULL) 33862306a36Sopenharmony_ci return -EINVAL; 33962306a36Sopenharmony_ci if (cmd->base.port != PORT_MII) 34062306a36Sopenharmony_ci return -EINVAL; 34162306a36Sopenharmony_ci if (cmd->base.phy_address != mii->phy_id) 34262306a36Sopenharmony_ci return -EINVAL; 34362306a36Sopenharmony_ci if (cmd->base.autoneg != AUTONEG_DISABLE && 34462306a36Sopenharmony_ci cmd->base.autoneg != AUTONEG_ENABLE) 34562306a36Sopenharmony_ci return -EINVAL; 34662306a36Sopenharmony_ci if ((speed == SPEED_1000) && (!mii->supports_gmii)) 34762306a36Sopenharmony_ci return -EINVAL; 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci /* ignore supported, maxtxpkt, maxrxpkt */ 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci if (cmd->base.autoneg == AUTONEG_ENABLE) { 35262306a36Sopenharmony_ci u32 bmcr, advert, tmp; 35362306a36Sopenharmony_ci u32 advert2 = 0, tmp2 = 0; 35462306a36Sopenharmony_ci u32 advertising; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci ethtool_convert_link_mode_to_legacy_u32( 35762306a36Sopenharmony_ci &advertising, cmd->link_modes.advertising); 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci if ((advertising & (ADVERTISED_10baseT_Half | 36062306a36Sopenharmony_ci ADVERTISED_10baseT_Full | 36162306a36Sopenharmony_ci ADVERTISED_100baseT_Half | 36262306a36Sopenharmony_ci ADVERTISED_100baseT_Full | 36362306a36Sopenharmony_ci ADVERTISED_1000baseT_Half | 36462306a36Sopenharmony_ci ADVERTISED_1000baseT_Full)) == 0) 36562306a36Sopenharmony_ci return -EINVAL; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci /* advertise only what has been requested */ 36862306a36Sopenharmony_ci advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE); 36962306a36Sopenharmony_ci tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4); 37062306a36Sopenharmony_ci if (mii->supports_gmii) { 37162306a36Sopenharmony_ci advert2 = mii->mdio_read(dev, mii->phy_id, 37262306a36Sopenharmony_ci MII_CTRL1000); 37362306a36Sopenharmony_ci tmp2 = advert2 & 37462306a36Sopenharmony_ci ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL); 37562306a36Sopenharmony_ci } 37662306a36Sopenharmony_ci tmp |= ethtool_adv_to_mii_adv_t(advertising); 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci if (mii->supports_gmii) 37962306a36Sopenharmony_ci tmp2 |= ethtool_adv_to_mii_ctrl1000_t(advertising); 38062306a36Sopenharmony_ci if (advert != tmp) { 38162306a36Sopenharmony_ci mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp); 38262306a36Sopenharmony_ci mii->advertising = tmp; 38362306a36Sopenharmony_ci } 38462306a36Sopenharmony_ci if ((mii->supports_gmii) && (advert2 != tmp2)) 38562306a36Sopenharmony_ci mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2); 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci /* turn on autonegotiation, and force a renegotiate */ 38862306a36Sopenharmony_ci bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR); 38962306a36Sopenharmony_ci bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART); 39062306a36Sopenharmony_ci mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr); 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci mii->force_media = 0; 39362306a36Sopenharmony_ci } else { 39462306a36Sopenharmony_ci u32 bmcr, tmp; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci /* turn off auto negotiation, set speed and duplexity */ 39762306a36Sopenharmony_ci bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR); 39862306a36Sopenharmony_ci tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 | 39962306a36Sopenharmony_ci BMCR_SPEED1000 | BMCR_FULLDPLX); 40062306a36Sopenharmony_ci if (speed == SPEED_1000) 40162306a36Sopenharmony_ci tmp |= BMCR_SPEED1000; 40262306a36Sopenharmony_ci else if (speed == SPEED_100) 40362306a36Sopenharmony_ci tmp |= BMCR_SPEED100; 40462306a36Sopenharmony_ci if (cmd->base.duplex == DUPLEX_FULL) { 40562306a36Sopenharmony_ci tmp |= BMCR_FULLDPLX; 40662306a36Sopenharmony_ci mii->full_duplex = 1; 40762306a36Sopenharmony_ci } else { 40862306a36Sopenharmony_ci mii->full_duplex = 0; 40962306a36Sopenharmony_ci } 41062306a36Sopenharmony_ci if (bmcr != tmp) 41162306a36Sopenharmony_ci mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp); 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci mii->force_media = 1; 41462306a36Sopenharmony_ci } 41562306a36Sopenharmony_ci return 0; 41662306a36Sopenharmony_ci} 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci/** 41962306a36Sopenharmony_ci * mii_check_gmii_support - check if the MII supports Gb interfaces 42062306a36Sopenharmony_ci * @mii: the MII interface 42162306a36Sopenharmony_ci */ 42262306a36Sopenharmony_ciint mii_check_gmii_support(struct mii_if_info *mii) 42362306a36Sopenharmony_ci{ 42462306a36Sopenharmony_ci int reg; 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci reg = mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR); 42762306a36Sopenharmony_ci if (reg & BMSR_ESTATEN) { 42862306a36Sopenharmony_ci reg = mii->mdio_read(mii->dev, mii->phy_id, MII_ESTATUS); 42962306a36Sopenharmony_ci if (reg & (ESTATUS_1000_TFULL | ESTATUS_1000_THALF)) 43062306a36Sopenharmony_ci return 1; 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci return 0; 43462306a36Sopenharmony_ci} 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci/** 43762306a36Sopenharmony_ci * mii_link_ok - is link status up/ok 43862306a36Sopenharmony_ci * @mii: the MII interface 43962306a36Sopenharmony_ci * 44062306a36Sopenharmony_ci * Returns 1 if the MII reports link status up/ok, 0 otherwise. 44162306a36Sopenharmony_ci */ 44262306a36Sopenharmony_ciint mii_link_ok (struct mii_if_info *mii) 44362306a36Sopenharmony_ci{ 44462306a36Sopenharmony_ci /* first, a dummy read, needed to latch some MII phys */ 44562306a36Sopenharmony_ci mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR); 44662306a36Sopenharmony_ci if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS) 44762306a36Sopenharmony_ci return 1; 44862306a36Sopenharmony_ci return 0; 44962306a36Sopenharmony_ci} 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci/** 45262306a36Sopenharmony_ci * mii_nway_restart - restart NWay (autonegotiation) for this interface 45362306a36Sopenharmony_ci * @mii: the MII interface 45462306a36Sopenharmony_ci * 45562306a36Sopenharmony_ci * Returns 0 on success, negative on error. 45662306a36Sopenharmony_ci */ 45762306a36Sopenharmony_ciint mii_nway_restart (struct mii_if_info *mii) 45862306a36Sopenharmony_ci{ 45962306a36Sopenharmony_ci int bmcr; 46062306a36Sopenharmony_ci int r = -EINVAL; 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci /* if autoneg is off, it's an error */ 46362306a36Sopenharmony_ci bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR); 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci if (bmcr & BMCR_ANENABLE) { 46662306a36Sopenharmony_ci bmcr |= BMCR_ANRESTART; 46762306a36Sopenharmony_ci mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr); 46862306a36Sopenharmony_ci r = 0; 46962306a36Sopenharmony_ci } 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci return r; 47262306a36Sopenharmony_ci} 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci/** 47562306a36Sopenharmony_ci * mii_check_link - check MII link status 47662306a36Sopenharmony_ci * @mii: MII interface 47762306a36Sopenharmony_ci * 47862306a36Sopenharmony_ci * If the link status changed (previous != current), call 47962306a36Sopenharmony_ci * netif_carrier_on() if current link status is Up or call 48062306a36Sopenharmony_ci * netif_carrier_off() if current link status is Down. 48162306a36Sopenharmony_ci */ 48262306a36Sopenharmony_civoid mii_check_link (struct mii_if_info *mii) 48362306a36Sopenharmony_ci{ 48462306a36Sopenharmony_ci int cur_link = mii_link_ok(mii); 48562306a36Sopenharmony_ci int prev_link = netif_carrier_ok(mii->dev); 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci if (cur_link && !prev_link) 48862306a36Sopenharmony_ci netif_carrier_on(mii->dev); 48962306a36Sopenharmony_ci else if (prev_link && !cur_link) 49062306a36Sopenharmony_ci netif_carrier_off(mii->dev); 49162306a36Sopenharmony_ci} 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci/** 49462306a36Sopenharmony_ci * mii_check_media - check the MII interface for a carrier/speed/duplex change 49562306a36Sopenharmony_ci * @mii: the MII interface 49662306a36Sopenharmony_ci * @ok_to_print: OK to print link up/down messages 49762306a36Sopenharmony_ci * @init_media: OK to save duplex mode in @mii 49862306a36Sopenharmony_ci * 49962306a36Sopenharmony_ci * Returns 1 if the duplex mode changed, 0 if not. 50062306a36Sopenharmony_ci * If the media type is forced, always returns 0. 50162306a36Sopenharmony_ci */ 50262306a36Sopenharmony_ciunsigned int mii_check_media (struct mii_if_info *mii, 50362306a36Sopenharmony_ci unsigned int ok_to_print, 50462306a36Sopenharmony_ci unsigned int init_media) 50562306a36Sopenharmony_ci{ 50662306a36Sopenharmony_ci unsigned int old_carrier, new_carrier; 50762306a36Sopenharmony_ci int advertise, lpa, media, duplex; 50862306a36Sopenharmony_ci int lpa2 = 0; 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci /* check current and old link status */ 51162306a36Sopenharmony_ci old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0; 51262306a36Sopenharmony_ci new_carrier = (unsigned int) mii_link_ok(mii); 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci /* if carrier state did not change, this is a "bounce", 51562306a36Sopenharmony_ci * just exit as everything is already set correctly 51662306a36Sopenharmony_ci */ 51762306a36Sopenharmony_ci if ((!init_media) && (old_carrier == new_carrier)) 51862306a36Sopenharmony_ci return 0; /* duplex did not change */ 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci /* no carrier, nothing much to do */ 52162306a36Sopenharmony_ci if (!new_carrier) { 52262306a36Sopenharmony_ci netif_carrier_off(mii->dev); 52362306a36Sopenharmony_ci if (ok_to_print) 52462306a36Sopenharmony_ci netdev_info(mii->dev, "link down\n"); 52562306a36Sopenharmony_ci return 0; /* duplex did not change */ 52662306a36Sopenharmony_ci } 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci /* 52962306a36Sopenharmony_ci * we have carrier, see who's on the other end 53062306a36Sopenharmony_ci */ 53162306a36Sopenharmony_ci netif_carrier_on(mii->dev); 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci if (mii->force_media) { 53462306a36Sopenharmony_ci if (ok_to_print) 53562306a36Sopenharmony_ci netdev_info(mii->dev, "link up\n"); 53662306a36Sopenharmony_ci return 0; /* duplex did not change */ 53762306a36Sopenharmony_ci } 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci /* get MII advertise and LPA values */ 54062306a36Sopenharmony_ci if ((!init_media) && (mii->advertising)) 54162306a36Sopenharmony_ci advertise = mii->advertising; 54262306a36Sopenharmony_ci else { 54362306a36Sopenharmony_ci advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE); 54462306a36Sopenharmony_ci mii->advertising = advertise; 54562306a36Sopenharmony_ci } 54662306a36Sopenharmony_ci lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA); 54762306a36Sopenharmony_ci if (mii->supports_gmii) 54862306a36Sopenharmony_ci lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000); 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci /* figure out media and duplex from advertise and LPA values */ 55162306a36Sopenharmony_ci media = mii_nway_result(lpa & advertise); 55262306a36Sopenharmony_ci duplex = (media & ADVERTISE_FULL) ? 1 : 0; 55362306a36Sopenharmony_ci if (lpa2 & LPA_1000FULL) 55462306a36Sopenharmony_ci duplex = 1; 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci if (ok_to_print) 55762306a36Sopenharmony_ci netdev_info(mii->dev, "link up, %uMbps, %s-duplex, lpa 0x%04X\n", 55862306a36Sopenharmony_ci lpa2 & (LPA_1000FULL | LPA_1000HALF) ? 1000 : 55962306a36Sopenharmony_ci media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ? 56062306a36Sopenharmony_ci 100 : 10, 56162306a36Sopenharmony_ci duplex ? "full" : "half", 56262306a36Sopenharmony_ci lpa); 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci if ((init_media) || (mii->full_duplex != duplex)) { 56562306a36Sopenharmony_ci mii->full_duplex = duplex; 56662306a36Sopenharmony_ci return 1; /* duplex changed */ 56762306a36Sopenharmony_ci } 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci return 0; /* duplex did not change */ 57062306a36Sopenharmony_ci} 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci/** 57362306a36Sopenharmony_ci * generic_mii_ioctl - main MII ioctl interface 57462306a36Sopenharmony_ci * @mii_if: the MII interface 57562306a36Sopenharmony_ci * @mii_data: MII ioctl data structure 57662306a36Sopenharmony_ci * @cmd: MII ioctl command 57762306a36Sopenharmony_ci * @duplex_chg_out: pointer to @duplex_changed status if there was no 57862306a36Sopenharmony_ci * ioctl error 57962306a36Sopenharmony_ci * 58062306a36Sopenharmony_ci * Returns 0 on success, negative on error. 58162306a36Sopenharmony_ci */ 58262306a36Sopenharmony_ciint generic_mii_ioctl(struct mii_if_info *mii_if, 58362306a36Sopenharmony_ci struct mii_ioctl_data *mii_data, int cmd, 58462306a36Sopenharmony_ci unsigned int *duplex_chg_out) 58562306a36Sopenharmony_ci{ 58662306a36Sopenharmony_ci int rc = 0; 58762306a36Sopenharmony_ci unsigned int duplex_changed = 0; 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci if (duplex_chg_out) 59062306a36Sopenharmony_ci *duplex_chg_out = 0; 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci mii_data->phy_id &= mii_if->phy_id_mask; 59362306a36Sopenharmony_ci mii_data->reg_num &= mii_if->reg_num_mask; 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci switch(cmd) { 59662306a36Sopenharmony_ci case SIOCGMIIPHY: 59762306a36Sopenharmony_ci mii_data->phy_id = mii_if->phy_id; 59862306a36Sopenharmony_ci fallthrough; 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci case SIOCGMIIREG: 60162306a36Sopenharmony_ci mii_data->val_out = 60262306a36Sopenharmony_ci mii_if->mdio_read(mii_if->dev, mii_data->phy_id, 60362306a36Sopenharmony_ci mii_data->reg_num); 60462306a36Sopenharmony_ci break; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci case SIOCSMIIREG: { 60762306a36Sopenharmony_ci u16 val = mii_data->val_in; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci if (mii_data->phy_id == mii_if->phy_id) { 61062306a36Sopenharmony_ci switch(mii_data->reg_num) { 61162306a36Sopenharmony_ci case MII_BMCR: { 61262306a36Sopenharmony_ci unsigned int new_duplex = 0; 61362306a36Sopenharmony_ci if (val & (BMCR_RESET|BMCR_ANENABLE)) 61462306a36Sopenharmony_ci mii_if->force_media = 0; 61562306a36Sopenharmony_ci else 61662306a36Sopenharmony_ci mii_if->force_media = 1; 61762306a36Sopenharmony_ci if (mii_if->force_media && 61862306a36Sopenharmony_ci (val & BMCR_FULLDPLX)) 61962306a36Sopenharmony_ci new_duplex = 1; 62062306a36Sopenharmony_ci if (mii_if->full_duplex != new_duplex) { 62162306a36Sopenharmony_ci duplex_changed = 1; 62262306a36Sopenharmony_ci mii_if->full_duplex = new_duplex; 62362306a36Sopenharmony_ci } 62462306a36Sopenharmony_ci break; 62562306a36Sopenharmony_ci } 62662306a36Sopenharmony_ci case MII_ADVERTISE: 62762306a36Sopenharmony_ci mii_if->advertising = val; 62862306a36Sopenharmony_ci break; 62962306a36Sopenharmony_ci default: 63062306a36Sopenharmony_ci /* do nothing */ 63162306a36Sopenharmony_ci break; 63262306a36Sopenharmony_ci } 63362306a36Sopenharmony_ci } 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci mii_if->mdio_write(mii_if->dev, mii_data->phy_id, 63662306a36Sopenharmony_ci mii_data->reg_num, val); 63762306a36Sopenharmony_ci break; 63862306a36Sopenharmony_ci } 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ci default: 64162306a36Sopenharmony_ci rc = -EOPNOTSUPP; 64262306a36Sopenharmony_ci break; 64362306a36Sopenharmony_ci } 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci if ((rc == 0) && (duplex_chg_out) && (duplex_changed)) 64662306a36Sopenharmony_ci *duplex_chg_out = 1; 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci return rc; 64962306a36Sopenharmony_ci} 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ciMODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>"); 65262306a36Sopenharmony_ciMODULE_DESCRIPTION ("MII hardware support library"); 65362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ciEXPORT_SYMBOL(mii_link_ok); 65662306a36Sopenharmony_ciEXPORT_SYMBOL(mii_nway_restart); 65762306a36Sopenharmony_ciEXPORT_SYMBOL(mii_ethtool_gset); 65862306a36Sopenharmony_ciEXPORT_SYMBOL(mii_ethtool_get_link_ksettings); 65962306a36Sopenharmony_ciEXPORT_SYMBOL(mii_ethtool_sset); 66062306a36Sopenharmony_ciEXPORT_SYMBOL(mii_ethtool_set_link_ksettings); 66162306a36Sopenharmony_ciEXPORT_SYMBOL(mii_check_link); 66262306a36Sopenharmony_ciEXPORT_SYMBOL(mii_check_media); 66362306a36Sopenharmony_ciEXPORT_SYMBOL(mii_check_gmii_support); 66462306a36Sopenharmony_ciEXPORT_SYMBOL(generic_mii_ioctl); 66562306a36Sopenharmony_ci 666