Index: drivers/net/phy/phy.c =================================================================== --- drivers/net/phy/phy.c +++ linux-imx/drivers/net/phy/phy.c @@ -55,6 +55,7 @@ static const char *phy_state_to_str(enum PHY_STATE_STR(NOLINK) PHY_STATE_STR(FORCING) PHY_STATE_STR(CHANGELINK) + PHY_STATE_STR(POWERED_DOWN) PHY_STATE_STR(HALTED) PHY_STATE_STR(RESUMING) } @@ -241,6 +242,33 @@ static void phy_sanitize_settings(struct } } +static int phy_shut(struct phy_device *phydev) +{ + mutex_lock(&phydev->lock); + phydev->power = PHY_POWER_DOWN; + if (PHY_HALTED == phydev->state) { + mutex_unlock(&phydev->lock); + return 0; + } + phydev->state = PHY_POWERED_DOWN; + mutex_unlock(&phydev->lock); + return 0; +} + +static int phy_unshut(struct phy_device *phydev) +{ + mutex_lock(&phydev->lock); + phydev->power = PHY_POWER_UP; + if (PHY_HALTED == phydev->state) { + mutex_unlock(&phydev->lock); + return 0; + } + phy_power_up(phydev); + phydev->state = PHY_CHANGELINK; + mutex_unlock(&phydev->lock); + return 0; +} + /** * phy_ethtool_sset - generic ethtool sset function, handles all the details * @phydev: target phy_device struct @@ -257,10 +285,28 @@ static void phy_sanitize_settings(struct int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd) { u32 speed = ethtool_cmd_speed(cmd); + int ret; if (cmd->phy_address != phydev->mdio.addr) return -EINVAL; + if (PHY_POWERED_DOWN == phydev->state) { + if (cmd->power == PHY_POWER_UP) { + ret = phy_unshut(phydev); + if (ret < 0) + return -EIO; + phy_trigger_machine(phydev, true); + } + return 0; + } + else if (cmd->power == PHY_POWER_DOWN) { + ret = phy_shut(phydev); + if (ret < 0) + return -EIO; + phy_trigger_machine(phydev, true); + return 0; + } + /* We make sure that we don't pass unsupported values in to the PHY */ cmd->advertising &= phydev->supported; @@ -308,10 +354,28 @@ int phy_ethtool_ksettings_set(struct phy u8 duplex = cmd->base.duplex; u32 speed = cmd->base.speed; u32 advertising; + int ret; if (cmd->base.phy_address != phydev->mdio.addr) return -EINVAL; + if (PHY_POWERED_DOWN == phydev->state) { + if (cmd->base.power == PHY_POWER_UP) { + ret = phy_unshut(phydev); + if (ret < 0) + return -EIO; + phy_trigger_machine(phydev, true); + } + return 0; + } + else if (cmd->base.power == PHY_POWER_DOWN) { + ret = phy_shut(phydev); + if (ret < 0) + return -EIO; + phy_trigger_machine(phydev, true); + return 0; + } + ethtool_convert_link_mode_to_legacy_u32(&advertising, cmd->link_modes.advertising); @@ -379,6 +443,7 @@ void phy_ethtool_ksettings_get(struct ph cmd->base.autoneg = phydev->autoneg; cmd->base.eth_tp_mdix_ctrl = phydev->mdix_ctrl; cmd->base.eth_tp_mdix = phydev->mdix; + cmd->base.power = phydev->power; } EXPORT_SYMBOL(phy_ethtool_ksettings_get); @@ -881,7 +946,7 @@ void phy_state_machine(struct work_struc struct delayed_work *dwork = to_delayed_work(work); struct phy_device *phydev = container_of(dwork, struct phy_device, state_queue); - bool needs_aneg = false, do_suspend = false; + bool needs_aneg = false, do_suspend = false, do_power_down = false; enum phy_state old_state; int err = 0; int old_link; @@ -1014,6 +1079,14 @@ void phy_state_machine(struct work_struc do_suspend = true; } break; + case PHY_POWERED_DOWN: + phy_read_status(phydev); + if (phydev->link) { + phydev->link = 0; + phy_link_down(phydev, true); + do_power_down = true; + } + break; case PHY_RESUMING: if (AUTONEG_ENABLE == phydev->autoneg) { err = phy_aneg_done(phydev); @@ -1059,8 +1132,14 @@ void phy_state_machine(struct work_struc if (needs_aneg) err = phy_start_aneg_priv(phydev, false); - else if (do_suspend) - phy_suspend(phydev); + else { + if (do_suspend) + phy_suspend(phydev); + if (do_power_down) { + phy_power_down(phydev); + phy_read_status(phydev); + } + } if (err < 0) phy_error(phydev); Index: drivers/net/phy/phy_device.c =================================================================== --- drivers/net/phy/phy_device.c +++ linux-imx/drivers/net/phy/phy_device.c @@ -1230,6 +1230,31 @@ out: } EXPORT_SYMBOL(phy_loopback); +int phy_power_down(struct phy_device *phydev) +{ + struct phy_driver *phydrv = to_phy_driver(phydev->mdio.dev.driver); + int ret = 0; + + if (phydev->drv && phydrv->suspend) + ret = phydrv->suspend(phydev); + + return ret; + +} +EXPORT_SYMBOL(phy_power_down); + +int phy_power_up(struct phy_device *phydev) +{ + struct phy_driver *phydrv = to_phy_driver(phydev->mdio.dev.driver); + int ret = 0; + + if (phydev->drv && phydrv->resume) + ret = phydrv->resume(phydev); + + return ret; +} +EXPORT_SYMBOL(phy_power_up); + /* Generic PHY support and helper functions */ /** @@ -1501,6 +1526,7 @@ EXPORT_SYMBOL(genphy_update_link); */ int genphy_read_status(struct phy_device *phydev) { + int bmcr; int adv; int err; int lpa; @@ -1513,6 +1539,10 @@ int genphy_read_status(struct phy_device if (err) return err; + bmcr = phy_read(phydev, MII_BMCR); + if (bmcr < 0) + return bmcr; + phydev->lp_advertising = 0; if (AUTONEG_ENABLE == phydev->autoneg) { @@ -1567,11 +1597,6 @@ int genphy_read_status(struct phy_device phydev->asym_pause = lpa & LPA_PAUSE_ASYM ? 1 : 0; } } else { - int bmcr = phy_read(phydev, MII_BMCR); - - if (bmcr < 0) - return bmcr; - if (bmcr & BMCR_FULLDPLX) phydev->duplex = DUPLEX_FULL; else @@ -1588,6 +1613,11 @@ int genphy_read_status(struct phy_device phydev->asym_pause = 0; } + if (bmcr & BMCR_PDOWN) + phydev->power = PHY_POWER_DOWN; + else + phydev->power = PHY_POWER_UP; + return 0; } EXPORT_SYMBOL(genphy_read_status); Index: include/linux/phy.h =================================================================== --- include/linux/phy.h +++ linux-imx/include/linux/phy.h @@ -348,6 +348,7 @@ enum phy_state { PHY_NOLINK, PHY_FORCING, PHY_CHANGELINK, + PHY_POWERED_DOWN, PHY_HALTED, PHY_RESUMING }; @@ -447,6 +448,8 @@ struct phy_device { int link_timeout; + int power; + #ifdef CONFIG_LED_TRIGGER_PHY struct phy_led_trigger *phy_led_triggers; unsigned int phy_num_led_triggers; @@ -819,6 +822,8 @@ int phy_suspend(struct phy_device *phyde int phy_resume(struct phy_device *phydev); int __phy_resume(struct phy_device *phydev); int phy_loopback(struct phy_device *phydev, bool enable); +int phy_power_up(struct phy_device *phydev); +int phy_power_down(struct phy_device *phydev); struct phy_device *phy_attach(struct net_device *dev, const char *bus_id, phy_interface_t interface); struct phy_device *phy_find_first(struct mii_bus *bus); Index: include/uapi/linux/ethtool.h =================================================================== --- include/uapi/linux/ethtool.h +++ linux-imx/include/uapi/linux/ethtool.h @@ -114,7 +114,10 @@ struct ethtool_cmd { __u8 eth_tp_mdix; __u8 eth_tp_mdix_ctrl; __u32 lp_advertising; - __u32 reserved[2]; + __u8 power; + __u8 reserved2; + __u16 reserved3; + __u32 reserved[1]; }; static inline void ethtool_cmd_speed_set(struct ethtool_cmd *ep, @@ -1664,6 +1667,11 @@ static inline int ethtool_validate_duple #define ETH_MODULE_SFF_8436 0x4 #define ETH_MODULE_SFF_8436_LEN 256 +/* TIESSE Power, up or down. */ +#define PHY_POWER_IGNORE 0x0 +#define PHY_POWER_DOWN 0x1 +#define PHY_POWER_UP 0x2 + /* Reset flags */ /* The reset() operation must clear the flags for the components which * were actually reset. On successful return, the flags indicate the @@ -1808,7 +1816,8 @@ struct ethtool_link_settings { __u8 eth_tp_mdix_ctrl; __s8 link_mode_masks_nwords; __u8 transceiver; - __u8 reserved1[3]; + __u8 power; + __u8 reserved1[2]; __u32 reserved[7]; __u32 link_mode_masks[0]; /* layout of link_mode_masks fields: Index: net/core/ethtool.c =================================================================== --- net/core/ethtool.c +++ linux-imx/net/core/ethtool.c @@ -480,6 +480,8 @@ convert_legacy_settings_to_link_ksetting = legacy_settings->eth_tp_mdix; link_ksettings->base.eth_tp_mdix_ctrl = legacy_settings->eth_tp_mdix_ctrl; + link_ksettings->base.power + = legacy_settings->power; return retval; } @@ -526,6 +528,8 @@ convert_link_ksettings_to_legacy_setting = link_ksettings->base.eth_tp_mdix_ctrl; legacy_settings->transceiver = link_ksettings->base.transceiver; + legacy_settings->power + = link_ksettings->base.power; return retval; }