forked from mirrors/linux
		
	Should of_mdiobus_register() fail, a previous usb_get_dev() call should be
undone as in the .disconnect function.
Fixes: 04e37d92fb ("net: phy: add marvell usb to mdio controller")
Signed-off-by: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
Reviewed-by: Simon Horman <simon.horman@corigine.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
		
	
			
		
			
				
	
	
		
			123 lines
		
	
	
	
		
			2.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			123 lines
		
	
	
	
		
			2.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0
 | 
						|
 | 
						|
#include <linux/kernel.h>
 | 
						|
#include <linux/module.h>
 | 
						|
#include <linux/of_mdio.h>
 | 
						|
#include <linux/phy.h>
 | 
						|
#include <linux/usb.h>
 | 
						|
 | 
						|
#define USB_MARVELL_VID	0x1286
 | 
						|
 | 
						|
static const struct usb_device_id mvusb_mdio_table[] = {
 | 
						|
	{ USB_DEVICE(USB_MARVELL_VID, 0x1fa4) },
 | 
						|
 | 
						|
	{}
 | 
						|
};
 | 
						|
MODULE_DEVICE_TABLE(usb, mvusb_mdio_table);
 | 
						|
 | 
						|
enum {
 | 
						|
	MVUSB_CMD_PREAMBLE0,
 | 
						|
	MVUSB_CMD_PREAMBLE1,
 | 
						|
	MVUSB_CMD_ADDR,
 | 
						|
	MVUSB_CMD_VAL,
 | 
						|
};
 | 
						|
 | 
						|
struct mvusb_mdio {
 | 
						|
	struct usb_device *udev;
 | 
						|
	struct mii_bus *mdio;
 | 
						|
 | 
						|
	__le16 buf[4];
 | 
						|
};
 | 
						|
 | 
						|
static int mvusb_mdio_read(struct mii_bus *mdio, int dev, int reg)
 | 
						|
{
 | 
						|
	struct mvusb_mdio *mvusb = mdio->priv;
 | 
						|
	int err, alen;
 | 
						|
 | 
						|
	mvusb->buf[MVUSB_CMD_ADDR] = cpu_to_le16(0xa400 | (dev << 5) | reg);
 | 
						|
 | 
						|
	err = usb_bulk_msg(mvusb->udev, usb_sndbulkpipe(mvusb->udev, 2),
 | 
						|
			   mvusb->buf, 6, &alen, 100);
 | 
						|
	if (err)
 | 
						|
		return err;
 | 
						|
 | 
						|
	err = usb_bulk_msg(mvusb->udev, usb_rcvbulkpipe(mvusb->udev, 6),
 | 
						|
			   &mvusb->buf[MVUSB_CMD_VAL], 2, &alen, 100);
 | 
						|
	if (err)
 | 
						|
		return err;
 | 
						|
 | 
						|
	return le16_to_cpu(mvusb->buf[MVUSB_CMD_VAL]);
 | 
						|
}
 | 
						|
 | 
						|
static int mvusb_mdio_write(struct mii_bus *mdio, int dev, int reg, u16 val)
 | 
						|
{
 | 
						|
	struct mvusb_mdio *mvusb = mdio->priv;
 | 
						|
	int alen;
 | 
						|
 | 
						|
	mvusb->buf[MVUSB_CMD_ADDR] = cpu_to_le16(0x8000 | (dev << 5) | reg);
 | 
						|
	mvusb->buf[MVUSB_CMD_VAL]  = cpu_to_le16(val);
 | 
						|
 | 
						|
	return usb_bulk_msg(mvusb->udev, usb_sndbulkpipe(mvusb->udev, 2),
 | 
						|
			    mvusb->buf, 8, &alen, 100);
 | 
						|
}
 | 
						|
 | 
						|
static int mvusb_mdio_probe(struct usb_interface *interface,
 | 
						|
			    const struct usb_device_id *id)
 | 
						|
{
 | 
						|
	struct device *dev = &interface->dev;
 | 
						|
	struct mvusb_mdio *mvusb;
 | 
						|
	struct mii_bus *mdio;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	mdio = devm_mdiobus_alloc_size(dev, sizeof(*mvusb));
 | 
						|
	if (!mdio)
 | 
						|
		return -ENOMEM;
 | 
						|
 | 
						|
	mvusb = mdio->priv;
 | 
						|
	mvusb->mdio = mdio;
 | 
						|
	mvusb->udev = usb_get_dev(interface_to_usbdev(interface));
 | 
						|
 | 
						|
	/* Reversed from USB PCAPs, no idea what these mean. */
 | 
						|
	mvusb->buf[MVUSB_CMD_PREAMBLE0] = cpu_to_le16(0xe800);
 | 
						|
	mvusb->buf[MVUSB_CMD_PREAMBLE1] = cpu_to_le16(0x0001);
 | 
						|
 | 
						|
	snprintf(mdio->id, MII_BUS_ID_SIZE, "mvusb-%s", dev_name(dev));
 | 
						|
	mdio->name = mdio->id;
 | 
						|
	mdio->parent = dev;
 | 
						|
	mdio->read = mvusb_mdio_read;
 | 
						|
	mdio->write = mvusb_mdio_write;
 | 
						|
 | 
						|
	usb_set_intfdata(interface, mvusb);
 | 
						|
	ret = of_mdiobus_register(mdio, dev->of_node);
 | 
						|
	if (ret)
 | 
						|
		goto put_dev;
 | 
						|
 | 
						|
	return 0;
 | 
						|
 | 
						|
put_dev:
 | 
						|
	usb_put_dev(mvusb->udev);
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static void mvusb_mdio_disconnect(struct usb_interface *interface)
 | 
						|
{
 | 
						|
	struct mvusb_mdio *mvusb = usb_get_intfdata(interface);
 | 
						|
	struct usb_device *udev = mvusb->udev;
 | 
						|
 | 
						|
	mdiobus_unregister(mvusb->mdio);
 | 
						|
	usb_set_intfdata(interface, NULL);
 | 
						|
	usb_put_dev(udev);
 | 
						|
}
 | 
						|
 | 
						|
static struct usb_driver mvusb_mdio_driver = {
 | 
						|
	.name       = "mvusb_mdio",
 | 
						|
	.id_table   = mvusb_mdio_table,
 | 
						|
	.probe      = mvusb_mdio_probe,
 | 
						|
	.disconnect = mvusb_mdio_disconnect,
 | 
						|
};
 | 
						|
 | 
						|
module_usb_driver(mvusb_mdio_driver);
 | 
						|
 | 
						|
MODULE_AUTHOR("Tobias Waldekranz <tobias@waldekranz.com>");
 | 
						|
MODULE_DESCRIPTION("Marvell USB MDIO Adapter");
 | 
						|
MODULE_LICENSE("GPL");
 |