diff mbox series

[net-next,v2,3/6] smsc95xx: add PAL support to use external PHY drivers

Message ID 20200723115507.26194-4-andre.edich@microchip.com
State Changes Requested
Delegated to: David Miller
Headers show
Series Add PAL support to smsc95xx | expand

Commit Message

Andre Edich July 23, 2020, 11:55 a.m. UTC
Generally, each PHY has their own configuration and it can be done
through an external PHY driver.  The smsc95xx driver uses only the
hard-coded internal PHY configuration.

This patch adds PAL (PHY Abstraction Layer) support to probe external
PHY drivers for configuring external PHYs.

Signed-off-by: Andre Edich <andre.edich@microchip.com>
---
 drivers/net/usb/smsc95xx.c | 201 ++++++++++++++++++++++++-------------
 1 file changed, 132 insertions(+), 69 deletions(-)

Comments

Andrew Lunn July 23, 2020, 10:39 p.m. UTC | #1
On Thu, Jul 23, 2020 at 01:55:04PM +0200, Andre Edich wrote:
> Generally, each PHY has their own configuration and it can be done
> through an external PHY driver.  The smsc95xx driver uses only the
> hard-coded internal PHY configuration.
> 
> This patch adds PAL (PHY Abstraction Layer) support to probe external
> PHY drivers for configuring external PHYs.

Hi Andre

We call it phylib, not PAL.

>  static int __must_check smsc95xx_wait_eeprom(struct usbnet *dev)
>  {
>  	unsigned long start_time = jiffies;
> @@ -559,15 +580,20 @@ static int smsc95xx_link_reset(struct usbnet *dev)
>  	u16 lcladv, rmtadv;
>  	int ret;
>  
> -	/* clear interrupt status */
> -	ret = smsc95xx_mdio_read(dev->net, mii->phy_id, PHY_INT_SRC);
> -	if (ret < 0)
> -		return ret;
> -
>  	ret = smsc95xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL_);
>  	if (ret < 0)
>  		return ret;
>  
> +	if (pdata->internal_phy) {
> +		/* clear interrupt status */
> +		ret = smsc95xx_mdio_read(dev->net, mii->phy_id, PHY_INT_SRC);
> +		if (ret < 0)
> +			return ret;
> +
> +		smsc95xx_mdio_write(dev->net, mii->phy_id, PHY_INT_MASK,
> +				    PHY_INT_MASK_DEFAULT_);
> +	}

The PHY driver should do this, not the MAC driver.

Which PHY driver is used for the internal PHY? In theory, you should
not need to know if it is internal or external, it is just a PHY. That
might mean you need to move some code from this driver into the PHY
driver, if it is currently missing in the PHY driver.

> +
>  	mii_check_media(mii, 1, 1);
>  	mii_ethtool_gset(&dev->mii, &ecmd);
>  	lcladv = smsc95xx_mdio_read(dev->net, mii->phy_id, MII_ADVERTISE);
> @@ -851,10 +877,10 @@ static int smsc95xx_get_link_ksettings(struct net_device *net,
>  	int retval;
>  
>  	retval = usbnet_get_link_ksettings(net, cmd);
> -
> -	cmd->base.eth_tp_mdix = pdata->mdix_ctrl;
> -	cmd->base.eth_tp_mdix_ctrl = pdata->mdix_ctrl;
> -
> +	if (pdata->internal_phy) {
> +		cmd->base.eth_tp_mdix = pdata->mdix_ctrl;
> +		cmd->base.eth_tp_mdix_ctrl = pdata->mdix_ctrl;
> +	}

Again, they PHY driver should take care of this. You need to set
phydev->mdix_ctrl before starting the PHY. The PHY driver should set
phdev->mdix to the current status. 

> +static void smsc95xx_handle_link_change(struct net_device *net)
> +{
> +	phy_print_status(net->phydev);

So the MAC does not care about the speed? The pause configuration?
Duplex?

	Andrew
Andre Edich July 24, 2020, 3:17 p.m. UTC | #2
On Fri, 2020-07-24 at 00:39 +0200, Andrew Lunn wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you
> know the content is safe
> 
> On Thu, Jul 23, 2020 at 01:55:04PM +0200, Andre Edich wrote:
> > Generally, each PHY has their own configuration and it can be done
> > through an external PHY driver.  The smsc95xx driver uses only the
> > hard-coded internal PHY configuration.
> > 
> > This patch adds PAL (PHY Abstraction Layer) support to probe
> > external
> > PHY drivers for configuring external PHYs.
> 
> Hi Andre
> 
> We call it phylib, not PAL.

Hi Andrew,

thank you for the feedback. In the next version, I will correct these
wordings as well.

> 
> >  static int __must_check smsc95xx_wait_eeprom(struct usbnet *dev)
> >  {
> >       unsigned long start_time = jiffies;
> > @@ -559,15 +580,20 @@ static int smsc95xx_link_reset(struct usbnet
> > *dev)
> >       u16 lcladv, rmtadv;
> >       int ret;
> > 
> > -     /* clear interrupt status */
> > -     ret = smsc95xx_mdio_read(dev->net, mii->phy_id, PHY_INT_SRC);
> > -     if (ret < 0)
> > -             return ret;
> > -
> >       ret = smsc95xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL_);
> >       if (ret < 0)
> >               return ret;
> > 
> > +     if (pdata->internal_phy) {
> > +             /* clear interrupt status */
> > +             ret = smsc95xx_mdio_read(dev->net, mii->phy_id,
> > PHY_INT_SRC);
> > +             if (ret < 0)
> > +                     return ret;
> > +
> > +             smsc95xx_mdio_write(dev->net, mii->phy_id,
> > PHY_INT_MASK,
> > +                                 PHY_INT_MASK_DEFAULT_);
> > +     }
> 
> The PHY driver should do this, not the MAC driver.
> 
> Which PHY driver is used for the internal PHY? In theory, you should
> not need to know if it is internal or external, it is just a PHY. 

Yes sure, you are right.  I see the drivers/net/phy/smsc.c that is
probed for the internal PHY of the DUT's Ethernet controller.

> 
> That
> might mean you need to move some code from this driver into the PHY
> driver, if it is currently missing in the PHY driver.

Correct, the PHY driver does interrupt setup activities, so that they
can be removed from the smsc95xx.
 
> 
> > +
> >       mii_check_media(mii, 1, 1);
> >       mii_ethtool_gset(&dev->mii, &ecmd);
> >       lcladv = smsc95xx_mdio_read(dev->net, mii->phy_id,
> > MII_ADVERTISE);
> > @@ -851,10 +877,10 @@ static int smsc95xx_get_link_ksettings(struct
> > net_device *net,
> >       int retval;
> > 
> >       retval = usbnet_get_link_ksettings(net, cmd);
> > -
> > -     cmd->base.eth_tp_mdix = pdata->mdix_ctrl;
> > -     cmd->base.eth_tp_mdix_ctrl = pdata->mdix_ctrl;
> > -
> > +     if (pdata->internal_phy) {
> > +             cmd->base.eth_tp_mdix = pdata->mdix_ctrl;
> > +             cmd->base.eth_tp_mdix_ctrl = pdata->mdix_ctrl;
> > +     }
> 
> Again, they PHY driver should take care of this. You need to set
> phydev->mdix_ctrl before starting the PHY. The PHY driver should set
> phdev->mdix to the current status.

The SMSC Phy driver does not have any MDIX setup code, but I think I've
got the idea now.

> 
> > +static void smsc95xx_handle_link_change(struct net_device *net)
> > +{
> > +     phy_print_status(net->phydev);
> 
> So the MAC does not care about the speed? The pause configuration?
> Duplex?

Now, I'm wondering how those "care about speed", "pause", and "duplex"
work in the current smsc95xx.  I guess, we did not touch any of those
activities with our patches.

Thanks a lot.
Andre

> 
>         Andrew
Andrew Lunn July 24, 2020, 3:34 p.m. UTC | #3
> > > +static void smsc95xx_handle_link_change(struct net_device *net)
> > > +{
> > > +     phy_print_status(net->phydev);
> > 
> > So the MAC does not care about the speed? The pause configuration?
> > Duplex?
> 
> Now, I'm wondering how those "care about speed", "pause", and "duplex"
> work in the current smsc95xx.  I guess, we did not touch any of those
> activities with our patches.

Yes, this patchset itself is not necessarily wrong. It seems more like
the driver could be broken with respect to these things. It is
something you might want to put on your TODO list to look at later.

	  Andrew
kernel test robot July 25, 2020, 4:33 p.m. UTC | #4
Hi Andre,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on net-next/master]

url:    https://github.com/0day-ci/linux/commits/Andre-Edich/Add-PAL-support-to-smsc95xx/20200723-195824
base:   https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git 7fc3b978a8971305d456b32d3f2ac13191f5a0d7
config: x86_64-randconfig-a016-20200724 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-14) 9.3.0
reproduce (this is a W=1 build):
        # save the attached .config to linux build tree
        make W=1 ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   ld: drivers/net/usb/smsc95xx.o: in function `smsc95xx_disconnect_phy':
>> drivers/net/usb/smsc95xx.c:1395: undefined reference to `phy_stop'
>> ld: drivers/net/usb/smsc95xx.c:1396: undefined reference to `phy_disconnect'
   ld: drivers/net/usb/smsc95xx.o: in function `smsc95xx_unbind':
>> drivers/net/usb/smsc95xx.c:1359: undefined reference to `mdiobus_unregister'
>> ld: drivers/net/usb/smsc95xx.c:1360: undefined reference to `mdiobus_free'
   ld: drivers/net/usb/smsc95xx.o: in function `smsc95xx_start_phy':
>> drivers/net/usb/smsc95xx.c:1380: undefined reference to `phy_connect_direct'
>> ld: drivers/net/usb/smsc95xx.c:1388: undefined reference to `phy_attached_info'
>> ld: drivers/net/usb/smsc95xx.c:1389: undefined reference to `phy_start'
   ld: drivers/net/usb/smsc95xx.o: in function `smsc95xx_resume':
>> drivers/net/usb/smsc95xx.c:1951: undefined reference to `phy_init_hw'
   ld: drivers/net/usb/smsc95xx.o: in function `smsc95xx_handle_link_change':
>> drivers/net/usb/smsc95xx.c:1367: undefined reference to `phy_print_status'
   ld: drivers/net/usb/smsc95xx.o: in function `mdiobus_alloc':
>> include/linux/phy.h:318: undefined reference to `mdiobus_alloc_size'
   ld: drivers/net/usb/smsc95xx.o: in function `smsc95xx_bind':
>> drivers/net/usb/smsc95xx.c:1293: undefined reference to `__mdiobus_register'
>> ld: drivers/net/usb/smsc95xx.c:1299: undefined reference to `phy_find_first'
   ld: drivers/net/usb/smsc95xx.c:1344: undefined reference to `mdiobus_unregister'
   ld: drivers/net/usb/smsc95xx.c:1347: undefined reference to `mdiobus_free'

vim +1395 drivers/net/usb/smsc95xx.c

  1222	
  1223	static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf)
  1224	{
  1225		struct smsc95xx_priv *pdata;
  1226		u32 val;
  1227		int ret;
  1228	
  1229		printk(KERN_INFO SMSC_CHIPNAME " v" SMSC_DRIVER_VERSION "\n");
  1230	
  1231		ret = usbnet_get_endpoints(dev, intf);
  1232		if (ret < 0) {
  1233			netdev_warn(dev->net, "usbnet_get_endpoints failed: %d\n", ret);
  1234			return ret;
  1235		}
  1236	
  1237		pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
  1238		if (!pdata)
  1239			return -ENOMEM;
  1240	
  1241		dev->driver_priv = pdata;
  1242	
  1243		spin_lock_init(&pdata->mac_cr_lock);
  1244	
  1245		/* LAN95xx devices do not alter the computed checksum of 0 to 0xffff.
  1246		 * RFC 2460, ipv6 UDP calculated checksum yields a result of zero must
  1247		 * be changed to 0xffff. RFC 768, ipv4 UDP computed checksum is zero,
  1248		 * it is transmitted as all ones. The zero transmitted checksum means
  1249		 * transmitter generated no checksum. Hence, enable csum offload only
  1250		 * for ipv4 packets.
  1251		 */
  1252		if (DEFAULT_TX_CSUM_ENABLE)
  1253			dev->net->features |= NETIF_F_IP_CSUM;
  1254		if (DEFAULT_RX_CSUM_ENABLE)
  1255			dev->net->features |= NETIF_F_RXCSUM;
  1256	
  1257		dev->net->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM;
  1258		set_bit(EVENT_NO_IP_ALIGN, &dev->flags);
  1259	
  1260		smsc95xx_init_mac_address(dev);
  1261	
  1262		/* Init all registers */
  1263		ret = smsc95xx_reset(dev);
  1264		if (ret)
  1265			goto free_pdata;
  1266	
  1267		pdata->mdiobus = mdiobus_alloc();
  1268		if (!pdata->mdiobus) {
  1269			ret = -ENOMEM;
  1270			goto free_pdata;
  1271		}
  1272	
  1273		ret = smsc95xx_read_reg(dev, HW_CFG, &val);
  1274		if (ret < 0)
  1275			goto free_mdio;
  1276	
  1277		pdata->internal_phy = !(val & HW_CFG_PSEL_);
  1278		if (pdata->internal_phy)
  1279			pdata->mdiobus->phy_mask = ~(1u << SMSC95XX_INTERNAL_PHY_ID);
  1280	
  1281		pdata->mdiobus->priv = dev;
  1282		pdata->mdiobus->read = smsc95xx_mdiobus_read;
  1283		pdata->mdiobus->write = smsc95xx_mdiobus_write;
  1284		pdata->mdiobus->name = "smsc95xx-mdiobus";
  1285		pdata->mdiobus->parent = &dev->udev->dev;
  1286	
  1287		dev->mii.phy_id_mask = 0x1f;
  1288		dev->mii.reg_num_mask = 0x1f;
  1289	
  1290		snprintf(pdata->mdiobus->id, ARRAY_SIZE(pdata->mdiobus->id),
  1291			 "usb-%03d:%03d", dev->udev->bus->busnum, dev->udev->devnum);
  1292	
> 1293		ret = mdiobus_register(pdata->mdiobus);
  1294		if (ret) {
  1295			netdev_err(dev->net, "Could not register MDIO bus\n");
  1296			goto free_mdio;
  1297		}
  1298	
> 1299		pdata->phydev = phy_find_first(pdata->mdiobus);
  1300		if (!pdata->phydev) {
  1301			netdev_err(dev->net, "no PHY found\n");
  1302			ret = -ENODEV;
  1303			goto unregister_mdio;
  1304		}
  1305	
  1306		dev->mii.dev = dev->net;
  1307		dev->mii.mdio_read = smsc95xx_mdio_read;
  1308		dev->mii.mdio_write = smsc95xx_mdio_write;
  1309		dev->mii.phy_id = pdata->phydev->mdio.addr;
  1310	
  1311		/* detect device revision as different features may be available */
  1312		ret = smsc95xx_read_reg(dev, ID_REV, &val);
  1313		if (ret < 0)
  1314			goto unregister_mdio;
  1315	
  1316		val >>= 16;
  1317		pdata->chip_id = val;
  1318		if (pdata->internal_phy)
  1319			pdata->mdix_ctrl = get_mdix_status(dev->net);
  1320	
  1321		if ((val == ID_REV_CHIP_ID_9500A_) || (val == ID_REV_CHIP_ID_9530_) ||
  1322		    (val == ID_REV_CHIP_ID_89530_) || (val == ID_REV_CHIP_ID_9730_))
  1323			pdata->features = (FEATURE_8_WAKEUP_FILTERS |
  1324				FEATURE_PHY_NLP_CROSSOVER |
  1325				FEATURE_REMOTE_WAKEUP);
  1326		else if (val == ID_REV_CHIP_ID_9512_)
  1327			pdata->features = FEATURE_8_WAKEUP_FILTERS;
  1328	
  1329		dev->net->netdev_ops = &smsc95xx_netdev_ops;
  1330		dev->net->ethtool_ops = &smsc95xx_ethtool_ops;
  1331		dev->net->flags |= IFF_MULTICAST;
  1332		dev->net->hard_header_len += SMSC95XX_TX_OVERHEAD_CSUM;
  1333		dev->net->min_mtu = ETH_MIN_MTU;
  1334		dev->net->max_mtu = ETH_DATA_LEN;
  1335		dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len;
  1336	
  1337		pdata->dev = dev;
  1338		INIT_DELAYED_WORK(&pdata->carrier_check, check_carrier);
  1339		schedule_delayed_work(&pdata->carrier_check, CARRIER_CHECK_DELAY);
  1340	
  1341		return 0;
  1342	
  1343	unregister_mdio:
  1344		mdiobus_unregister(pdata->mdiobus);
  1345	
  1346	free_mdio:
  1347		mdiobus_free(pdata->mdiobus);
  1348	
  1349	free_pdata:
  1350		kfree(pdata);
  1351		return ret;
  1352	}
  1353	
  1354	static void smsc95xx_unbind(struct usbnet *dev, struct usb_interface *intf)
  1355	{
  1356		struct smsc95xx_priv *pdata = dev->driver_priv;
  1357	
  1358		cancel_delayed_work_sync(&pdata->carrier_check);
> 1359		mdiobus_unregister(pdata->mdiobus);
> 1360		mdiobus_free(pdata->mdiobus);
  1361		netif_dbg(dev, ifdown, dev->net, "free pdata\n");
  1362		kfree(pdata);
  1363	}
  1364	
  1365	static void smsc95xx_handle_link_change(struct net_device *net)
  1366	{
> 1367		phy_print_status(net->phydev);
  1368	}
  1369	
  1370	static int smsc95xx_start_phy(struct usbnet *dev)
  1371	{
  1372		struct smsc95xx_priv *pdata = dev->driver_priv;
  1373		struct net_device *net = dev->net;
  1374		int ret;
  1375	
  1376		ret = smsc95xx_reset(dev);
  1377		if (ret < 0)
  1378			return ret;
  1379	
> 1380		ret = phy_connect_direct(net, pdata->phydev,
  1381					 &smsc95xx_handle_link_change,
  1382					 PHY_INTERFACE_MODE_MII);
  1383		if (ret) {
  1384			netdev_err(net, "can't attach PHY to %s\n", pdata->mdiobus->id);
  1385			return ret;
  1386		}
  1387	
> 1388		phy_attached_info(net->phydev);
> 1389		phy_start(net->phydev);
  1390		return 0;
  1391	}
  1392	
  1393	static int smsc95xx_disconnect_phy(struct usbnet *dev)
  1394	{
> 1395		phy_stop(dev->net->phydev);
> 1396		phy_disconnect(dev->net->phydev);
  1397		return 0;
  1398	}
  1399	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c
index f200684875fb..9d2710f6d396 100644
--- a/drivers/net/usb/smsc95xx.c
+++ b/drivers/net/usb/smsc95xx.c
@@ -18,6 +18,8 @@ 
 #include <linux/usb/usbnet.h>
 #include <linux/slab.h>
 #include <linux/of_net.h>
+#include <linux/mdio.h>
+#include <linux/phy.h>
 #include "smsc95xx.h"
 
 #define SMSC_CHIPNAME			"smsc95xx"
@@ -64,6 +66,9 @@  struct smsc95xx_priv {
 	bool link_ok;
 	struct delayed_work carrier_check;
 	struct usbnet *dev;
+	struct mii_bus *mdiobus;
+	struct phy_device *phydev;
+	bool internal_phy;
 };
 
 static bool turbo_mode = true;
@@ -286,6 +291,22 @@  static void smsc95xx_mdio_write(struct net_device *netdev, int phy_id, int idx,
 	__smsc95xx_mdio_write(netdev, phy_id, idx, regval, 0);
 }
 
+static int smsc95xx_mdiobus_read(struct mii_bus *bus, int phy_id, int idx)
+{
+	struct usbnet *dev = bus->priv;
+
+	return __smsc95xx_mdio_read(dev->net, phy_id, idx, 0);
+}
+
+static int smsc95xx_mdiobus_write(struct mii_bus *bus, int phy_id, int idx,
+				  u16 regval)
+{
+	struct usbnet *dev = bus->priv;
+
+	__smsc95xx_mdio_write(dev->net, phy_id, idx, regval, 0);
+	return 0;
+}
+
 static int __must_check smsc95xx_wait_eeprom(struct usbnet *dev)
 {
 	unsigned long start_time = jiffies;
@@ -559,15 +580,20 @@  static int smsc95xx_link_reset(struct usbnet *dev)
 	u16 lcladv, rmtadv;
 	int ret;
 
-	/* clear interrupt status */
-	ret = smsc95xx_mdio_read(dev->net, mii->phy_id, PHY_INT_SRC);
-	if (ret < 0)
-		return ret;
-
 	ret = smsc95xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL_);
 	if (ret < 0)
 		return ret;
 
+	if (pdata->internal_phy) {
+		/* clear interrupt status */
+		ret = smsc95xx_mdio_read(dev->net, mii->phy_id, PHY_INT_SRC);
+		if (ret < 0)
+			return ret;
+
+		smsc95xx_mdio_write(dev->net, mii->phy_id, PHY_INT_MASK,
+				    PHY_INT_MASK_DEFAULT_);
+	}
+
 	mii_check_media(mii, 1, 1);
 	mii_ethtool_gset(&dev->mii, &ecmd);
 	lcladv = smsc95xx_mdio_read(dev->net, mii->phy_id, MII_ADVERTISE);
@@ -851,10 +877,10 @@  static int smsc95xx_get_link_ksettings(struct net_device *net,
 	int retval;
 
 	retval = usbnet_get_link_ksettings(net, cmd);
-
-	cmd->base.eth_tp_mdix = pdata->mdix_ctrl;
-	cmd->base.eth_tp_mdix_ctrl = pdata->mdix_ctrl;
-
+	if (pdata->internal_phy) {
+		cmd->base.eth_tp_mdix = pdata->mdix_ctrl;
+		cmd->base.eth_tp_mdix_ctrl = pdata->mdix_ctrl;
+	}
 	return retval;
 }
 
@@ -863,14 +889,12 @@  static int smsc95xx_set_link_ksettings(struct net_device *net,
 {
 	struct usbnet *dev = netdev_priv(net);
 	struct smsc95xx_priv *pdata = dev->driver_priv;
-	int retval;
-
-	if (pdata->mdix_ctrl != cmd->base.eth_tp_mdix_ctrl)
-		set_mdix_status(net, cmd->base.eth_tp_mdix_ctrl);
+	u8 mdix_ctrl = cmd->base.eth_tp_mdix_ctrl;
 
-	retval = usbnet_set_link_ksettings(net, cmd);
+	if (pdata->mdix_ctrl != mdix_ctrl && pdata->internal_phy)
+		set_mdix_status(net, mdix_ctrl);
 
-	return retval;
+	return usbnet_set_link_ksettings(net, cmd);
 }
 
 static const struct ethtool_ops smsc95xx_ethtool_ops = {
@@ -974,51 +998,6 @@  static int smsc95xx_start_rx_path(struct usbnet *dev, int in_pm)
 	return __smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr, in_pm);
 }
 
-static int smsc95xx_phy_initialize(struct usbnet *dev)
-{
-	int bmcr, ret, timeout = 0;
-
-	/* Initialize MII structure */
-	dev->mii.dev = dev->net;
-	dev->mii.mdio_read = smsc95xx_mdio_read;
-	dev->mii.mdio_write = smsc95xx_mdio_write;
-	dev->mii.phy_id_mask = 0x1f;
-	dev->mii.reg_num_mask = 0x1f;
-	dev->mii.phy_id = SMSC95XX_INTERNAL_PHY_ID;
-
-	/* reset phy and wait for reset to complete */
-	smsc95xx_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET);
-
-	do {
-		msleep(10);
-		bmcr = smsc95xx_mdio_read(dev->net, dev->mii.phy_id, MII_BMCR);
-		timeout++;
-	} while ((bmcr & BMCR_RESET) && (timeout < 100));
-
-	if (timeout >= 100) {
-		netdev_warn(dev->net, "timeout on PHY Reset");
-		return -EIO;
-	}
-
-	smsc95xx_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE,
-		ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP |
-		ADVERTISE_PAUSE_ASYM);
-
-	/* read to clear */
-	ret = smsc95xx_mdio_read(dev->net, dev->mii.phy_id, PHY_INT_SRC);
-	if (ret < 0) {
-		netdev_warn(dev->net, "Failed to read PHY_INT_SRC during init\n");
-		return ret;
-	}
-
-	smsc95xx_mdio_write(dev->net, dev->mii.phy_id, PHY_INT_MASK,
-		PHY_INT_MASK_DEFAULT_);
-	mii_nway_restart(&dev->mii);
-
-	netif_dbg(dev, ifup, dev->net, "phy initialised successfully\n");
-	return 0;
-}
-
 static int smsc95xx_reset(struct usbnet *dev)
 {
 	struct smsc95xx_priv *pdata = dev->driver_priv;
@@ -1200,12 +1179,6 @@  static int smsc95xx_reset(struct usbnet *dev)
 
 	smsc95xx_set_multicast(dev->net);
 
-	ret = smsc95xx_phy_initialize(dev);
-	if (ret < 0) {
-		netdev_warn(dev->net, "Failed to init PHY\n");
-		return ret;
-	}
-
 	ret = smsc95xx_read_reg(dev, INT_EP_CTL, &read_buf);
 	if (ret < 0)
 		return ret;
@@ -1291,14 +1264,59 @@  static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf)
 	if (ret)
 		goto free_pdata;
 
+	pdata->mdiobus = mdiobus_alloc();
+	if (!pdata->mdiobus) {
+		ret = -ENOMEM;
+		goto free_pdata;
+	}
+
+	ret = smsc95xx_read_reg(dev, HW_CFG, &val);
+	if (ret < 0)
+		goto free_mdio;
+
+	pdata->internal_phy = !(val & HW_CFG_PSEL_);
+	if (pdata->internal_phy)
+		pdata->mdiobus->phy_mask = ~(1u << SMSC95XX_INTERNAL_PHY_ID);
+
+	pdata->mdiobus->priv = dev;
+	pdata->mdiobus->read = smsc95xx_mdiobus_read;
+	pdata->mdiobus->write = smsc95xx_mdiobus_write;
+	pdata->mdiobus->name = "smsc95xx-mdiobus";
+	pdata->mdiobus->parent = &dev->udev->dev;
+
+	dev->mii.phy_id_mask = 0x1f;
+	dev->mii.reg_num_mask = 0x1f;
+
+	snprintf(pdata->mdiobus->id, ARRAY_SIZE(pdata->mdiobus->id),
+		 "usb-%03d:%03d", dev->udev->bus->busnum, dev->udev->devnum);
+
+	ret = mdiobus_register(pdata->mdiobus);
+	if (ret) {
+		netdev_err(dev->net, "Could not register MDIO bus\n");
+		goto free_mdio;
+	}
+
+	pdata->phydev = phy_find_first(pdata->mdiobus);
+	if (!pdata->phydev) {
+		netdev_err(dev->net, "no PHY found\n");
+		ret = -ENODEV;
+		goto unregister_mdio;
+	}
+
+	dev->mii.dev = dev->net;
+	dev->mii.mdio_read = smsc95xx_mdio_read;
+	dev->mii.mdio_write = smsc95xx_mdio_write;
+	dev->mii.phy_id = pdata->phydev->mdio.addr;
+
 	/* detect device revision as different features may be available */
 	ret = smsc95xx_read_reg(dev, ID_REV, &val);
 	if (ret < 0)
-		goto free_pdata;
+		goto unregister_mdio;
 
 	val >>= 16;
 	pdata->chip_id = val;
-	pdata->mdix_ctrl = get_mdix_status(dev->net);
+	if (pdata->internal_phy)
+		pdata->mdix_ctrl = get_mdix_status(dev->net);
 
 	if ((val == ID_REV_CHIP_ID_9500A_) || (val == ID_REV_CHIP_ID_9530_) ||
 	    (val == ID_REV_CHIP_ID_89530_) || (val == ID_REV_CHIP_ID_9730_))
@@ -1322,6 +1340,12 @@  static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf)
 
 	return 0;
 
+unregister_mdio:
+	mdiobus_unregister(pdata->mdiobus);
+
+free_mdio:
+	mdiobus_free(pdata->mdiobus);
+
 free_pdata:
 	kfree(pdata);
 	return ret;
@@ -1332,10 +1356,47 @@  static void smsc95xx_unbind(struct usbnet *dev, struct usb_interface *intf)
 	struct smsc95xx_priv *pdata = dev->driver_priv;
 
 	cancel_delayed_work_sync(&pdata->carrier_check);
+	mdiobus_unregister(pdata->mdiobus);
+	mdiobus_free(pdata->mdiobus);
 	netif_dbg(dev, ifdown, dev->net, "free pdata\n");
 	kfree(pdata);
 }
 
+static void smsc95xx_handle_link_change(struct net_device *net)
+{
+	phy_print_status(net->phydev);
+}
+
+static int smsc95xx_start_phy(struct usbnet *dev)
+{
+	struct smsc95xx_priv *pdata = dev->driver_priv;
+	struct net_device *net = dev->net;
+	int ret;
+
+	ret = smsc95xx_reset(dev);
+	if (ret < 0)
+		return ret;
+
+	ret = phy_connect_direct(net, pdata->phydev,
+				 &smsc95xx_handle_link_change,
+				 PHY_INTERFACE_MODE_MII);
+	if (ret) {
+		netdev_err(net, "can't attach PHY to %s\n", pdata->mdiobus->id);
+		return ret;
+	}
+
+	phy_attached_info(net->phydev);
+	phy_start(net->phydev);
+	return 0;
+}
+
+static int smsc95xx_disconnect_phy(struct usbnet *dev)
+{
+	phy_stop(dev->net->phydev);
+	phy_disconnect(dev->net->phydev);
+	return 0;
+}
+
 static u32 smsc_crc(const u8 *buffer, size_t len, int filter)
 {
 	u32 crc = bitrev16(crc16(0xFFFF, buffer, len));
@@ -1887,6 +1948,7 @@  static int smsc95xx_resume(struct usb_interface *intf)
 	if (ret < 0)
 		netdev_warn(dev->net, "usbnet_resume error\n");
 
+	phy_init_hw(pdata->phydev);
 	return ret;
 }
 
@@ -2092,7 +2154,8 @@  static const struct driver_info smsc95xx_info = {
 	.bind		= smsc95xx_bind,
 	.unbind		= smsc95xx_unbind,
 	.link_reset	= smsc95xx_link_reset,
-	.reset		= smsc95xx_reset,
+	.reset		= smsc95xx_start_phy,
+	.stop		= smsc95xx_disconnect_phy,
 	.rx_fixup	= smsc95xx_rx_fixup,
 	.tx_fixup	= smsc95xx_tx_fixup,
 	.status		= smsc95xx_status,