1 // SPDX-License-Identifier: GPL-2.0
2
3 #include <linux/kernel.h>
4 #include <linux/module.h>
5 #include <linux/of_mdio.h>
6 #include <linux/phy.h>
7 #include <linux/usb.h>
8
9 #define USB_MARVELL_VID 0x1286
10
11 static const struct usb_device_id mvusb_mdio_table[] = {
12 { USB_DEVICE(USB_MARVELL_VID, 0x1fa4) },
13
14 {}
15 };
16 MODULE_DEVICE_TABLE(usb, mvusb_mdio_table);
17
18 enum {
19 MVUSB_CMD_PREAMBLE0,
20 MVUSB_CMD_PREAMBLE1,
21 MVUSB_CMD_ADDR,
22 MVUSB_CMD_VAL,
23 };
24
25 struct mvusb_mdio {
26 struct usb_device *udev;
27 struct mii_bus *mdio;
28
29 __le16 buf[4];
30 };
31
mvusb_mdio_read(struct mii_bus * mdio,int dev,int reg)32 static int mvusb_mdio_read(struct mii_bus *mdio, int dev, int reg)
33 {
34 struct mvusb_mdio *mvusb = mdio->priv;
35 int err, alen;
36
37 if (dev & MII_ADDR_C45)
38 return -EOPNOTSUPP;
39
40 mvusb->buf[MVUSB_CMD_ADDR] = cpu_to_le16(0xa400 | (dev << 5) | reg);
41
42 err = usb_bulk_msg(mvusb->udev, usb_sndbulkpipe(mvusb->udev, 2),
43 mvusb->buf, 6, &alen, 100);
44 if (err)
45 return err;
46
47 err = usb_bulk_msg(mvusb->udev, usb_rcvbulkpipe(mvusb->udev, 6),
48 &mvusb->buf[MVUSB_CMD_VAL], 2, &alen, 100);
49 if (err)
50 return err;
51
52 return le16_to_cpu(mvusb->buf[MVUSB_CMD_VAL]);
53 }
54
mvusb_mdio_write(struct mii_bus * mdio,int dev,int reg,u16 val)55 static int mvusb_mdio_write(struct mii_bus *mdio, int dev, int reg, u16 val)
56 {
57 struct mvusb_mdio *mvusb = mdio->priv;
58 int alen;
59
60 if (dev & MII_ADDR_C45)
61 return -EOPNOTSUPP;
62
63 mvusb->buf[MVUSB_CMD_ADDR] = cpu_to_le16(0x8000 | (dev << 5) | reg);
64 mvusb->buf[MVUSB_CMD_VAL] = cpu_to_le16(val);
65
66 return usb_bulk_msg(mvusb->udev, usb_sndbulkpipe(mvusb->udev, 2),
67 mvusb->buf, 8, &alen, 100);
68 }
69
mvusb_mdio_probe(struct usb_interface * interface,const struct usb_device_id * id)70 static int mvusb_mdio_probe(struct usb_interface *interface,
71 const struct usb_device_id *id)
72 {
73 struct device *dev = &interface->dev;
74 struct mvusb_mdio *mvusb;
75 struct mii_bus *mdio;
76
77 mdio = devm_mdiobus_alloc_size(dev, sizeof(*mvusb));
78 if (!mdio)
79 return -ENOMEM;
80
81 mvusb = mdio->priv;
82 mvusb->mdio = mdio;
83 mvusb->udev = usb_get_dev(interface_to_usbdev(interface));
84
85 /* Reversed from USB PCAPs, no idea what these mean. */
86 mvusb->buf[MVUSB_CMD_PREAMBLE0] = cpu_to_le16(0xe800);
87 mvusb->buf[MVUSB_CMD_PREAMBLE1] = cpu_to_le16(0x0001);
88
89 snprintf(mdio->id, MII_BUS_ID_SIZE, "mvusb-%s", dev_name(dev));
90 mdio->name = mdio->id;
91 mdio->parent = dev;
92 mdio->read = mvusb_mdio_read;
93 mdio->write = mvusb_mdio_write;
94
95 usb_set_intfdata(interface, mvusb);
96 return of_mdiobus_register(mdio, dev->of_node);
97 }
98
mvusb_mdio_disconnect(struct usb_interface * interface)99 static void mvusb_mdio_disconnect(struct usb_interface *interface)
100 {
101 struct mvusb_mdio *mvusb = usb_get_intfdata(interface);
102 struct usb_device *udev = mvusb->udev;
103
104 mdiobus_unregister(mvusb->mdio);
105 usb_set_intfdata(interface, NULL);
106 usb_put_dev(udev);
107 }
108
109 static struct usb_driver mvusb_mdio_driver = {
110 .name = "mvusb_mdio",
111 .id_table = mvusb_mdio_table,
112 .probe = mvusb_mdio_probe,
113 .disconnect = mvusb_mdio_disconnect,
114 };
115
116 module_usb_driver(mvusb_mdio_driver);
117
118 MODULE_AUTHOR("Tobias Waldekranz <tobias@waldekranz.com>");
119 MODULE_DESCRIPTION("Marvell USB MDIO Adapter");
120 MODULE_LICENSE("GPL");
121