162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#include <linux/kernel.h> 462306a36Sopenharmony_ci#include <linux/module.h> 562306a36Sopenharmony_ci#include <linux/of_mdio.h> 662306a36Sopenharmony_ci#include <linux/phy.h> 762306a36Sopenharmony_ci#include <linux/usb.h> 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#define USB_MARVELL_VID 0x1286 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_cistatic const struct usb_device_id mvusb_mdio_table[] = { 1262306a36Sopenharmony_ci { USB_DEVICE(USB_MARVELL_VID, 0x1fa4) }, 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci {} 1562306a36Sopenharmony_ci}; 1662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, mvusb_mdio_table); 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cienum { 1962306a36Sopenharmony_ci MVUSB_CMD_PREAMBLE0, 2062306a36Sopenharmony_ci MVUSB_CMD_PREAMBLE1, 2162306a36Sopenharmony_ci MVUSB_CMD_ADDR, 2262306a36Sopenharmony_ci MVUSB_CMD_VAL, 2362306a36Sopenharmony_ci}; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistruct mvusb_mdio { 2662306a36Sopenharmony_ci struct usb_device *udev; 2762306a36Sopenharmony_ci struct mii_bus *mdio; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci __le16 buf[4]; 3062306a36Sopenharmony_ci}; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic int mvusb_mdio_read(struct mii_bus *mdio, int dev, int reg) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci struct mvusb_mdio *mvusb = mdio->priv; 3562306a36Sopenharmony_ci int err, alen; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci mvusb->buf[MVUSB_CMD_ADDR] = cpu_to_le16(0xa400 | (dev << 5) | reg); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci err = usb_bulk_msg(mvusb->udev, usb_sndbulkpipe(mvusb->udev, 2), 4062306a36Sopenharmony_ci mvusb->buf, 6, &alen, 100); 4162306a36Sopenharmony_ci if (err) 4262306a36Sopenharmony_ci return err; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci err = usb_bulk_msg(mvusb->udev, usb_rcvbulkpipe(mvusb->udev, 6), 4562306a36Sopenharmony_ci &mvusb->buf[MVUSB_CMD_VAL], 2, &alen, 100); 4662306a36Sopenharmony_ci if (err) 4762306a36Sopenharmony_ci return err; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci return le16_to_cpu(mvusb->buf[MVUSB_CMD_VAL]); 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic int mvusb_mdio_write(struct mii_bus *mdio, int dev, int reg, u16 val) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci struct mvusb_mdio *mvusb = mdio->priv; 5562306a36Sopenharmony_ci int alen; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci mvusb->buf[MVUSB_CMD_ADDR] = cpu_to_le16(0x8000 | (dev << 5) | reg); 5862306a36Sopenharmony_ci mvusb->buf[MVUSB_CMD_VAL] = cpu_to_le16(val); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci return usb_bulk_msg(mvusb->udev, usb_sndbulkpipe(mvusb->udev, 2), 6162306a36Sopenharmony_ci mvusb->buf, 8, &alen, 100); 6262306a36Sopenharmony_ci} 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistatic int mvusb_mdio_probe(struct usb_interface *interface, 6562306a36Sopenharmony_ci const struct usb_device_id *id) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci struct device *dev = &interface->dev; 6862306a36Sopenharmony_ci struct mvusb_mdio *mvusb; 6962306a36Sopenharmony_ci struct mii_bus *mdio; 7062306a36Sopenharmony_ci int ret; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci mdio = devm_mdiobus_alloc_size(dev, sizeof(*mvusb)); 7362306a36Sopenharmony_ci if (!mdio) 7462306a36Sopenharmony_ci return -ENOMEM; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci mvusb = mdio->priv; 7762306a36Sopenharmony_ci mvusb->mdio = mdio; 7862306a36Sopenharmony_ci mvusb->udev = usb_get_dev(interface_to_usbdev(interface)); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci /* Reversed from USB PCAPs, no idea what these mean. */ 8162306a36Sopenharmony_ci mvusb->buf[MVUSB_CMD_PREAMBLE0] = cpu_to_le16(0xe800); 8262306a36Sopenharmony_ci mvusb->buf[MVUSB_CMD_PREAMBLE1] = cpu_to_le16(0x0001); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci snprintf(mdio->id, MII_BUS_ID_SIZE, "mvusb-%s", dev_name(dev)); 8562306a36Sopenharmony_ci mdio->name = mdio->id; 8662306a36Sopenharmony_ci mdio->parent = dev; 8762306a36Sopenharmony_ci mdio->read = mvusb_mdio_read; 8862306a36Sopenharmony_ci mdio->write = mvusb_mdio_write; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci usb_set_intfdata(interface, mvusb); 9162306a36Sopenharmony_ci ret = of_mdiobus_register(mdio, dev->of_node); 9262306a36Sopenharmony_ci if (ret) 9362306a36Sopenharmony_ci goto put_dev; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci return 0; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ciput_dev: 9862306a36Sopenharmony_ci usb_put_dev(mvusb->udev); 9962306a36Sopenharmony_ci return ret; 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic void mvusb_mdio_disconnect(struct usb_interface *interface) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci struct mvusb_mdio *mvusb = usb_get_intfdata(interface); 10562306a36Sopenharmony_ci struct usb_device *udev = mvusb->udev; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci mdiobus_unregister(mvusb->mdio); 10862306a36Sopenharmony_ci usb_set_intfdata(interface, NULL); 10962306a36Sopenharmony_ci usb_put_dev(udev); 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic struct usb_driver mvusb_mdio_driver = { 11362306a36Sopenharmony_ci .name = "mvusb_mdio", 11462306a36Sopenharmony_ci .id_table = mvusb_mdio_table, 11562306a36Sopenharmony_ci .probe = mvusb_mdio_probe, 11662306a36Sopenharmony_ci .disconnect = mvusb_mdio_disconnect, 11762306a36Sopenharmony_ci}; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cimodule_usb_driver(mvusb_mdio_driver); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ciMODULE_AUTHOR("Tobias Waldekranz <tobias@waldekranz.com>"); 12262306a36Sopenharmony_ciMODULE_DESCRIPTION("Marvell USB MDIO Adapter"); 12362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 124