| // 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; | |
| mdio = devm_mdiobus_alloc_size(dev, sizeof(*mvusb)); | |
| if (!mdio) | |
| return -ENOMEM; | |
| mvusb = mdio->priv; | |
| mvusb->mdio = mdio; | |
| mvusb->udev = 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); | |
| return of_mdiobus_register(mdio, dev->of_node); | |
| } | |
| static void mvusb_mdio_disconnect(struct usb_interface *interface) | |
| { | |
| struct mvusb_mdio *mvusb = usb_get_intfdata(interface); | |
| mdiobus_unregister(mvusb->mdio); | |
| usb_set_intfdata(interface, NULL); | |
| } | |
| 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"); |