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
11static const struct usb_device_id mvusb_mdio_table[] = {
12	{ USB_DEVICE(USB_MARVELL_VID, 0x1fa4) },
13
14	{}
15};
16MODULE_DEVICE_TABLE(usb, mvusb_mdio_table);
17
18enum {
19	MVUSB_CMD_PREAMBLE0,
20	MVUSB_CMD_PREAMBLE1,
21	MVUSB_CMD_ADDR,
22	MVUSB_CMD_VAL,
23};
24
25struct mvusb_mdio {
26	struct usb_device *udev;
27	struct mii_bus *mdio;
28
29	__le16 buf[4];
30};
31
32static 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
55static 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
70static 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	int ret;
77
78	mdio = devm_mdiobus_alloc_size(dev, sizeof(*mvusb));
79	if (!mdio)
80		return -ENOMEM;
81
82	mvusb = mdio->priv;
83	mvusb->mdio = mdio;
84	mvusb->udev = usb_get_dev(interface_to_usbdev(interface));
85
86	/* Reversed from USB PCAPs, no idea what these mean. */
87	mvusb->buf[MVUSB_CMD_PREAMBLE0] = cpu_to_le16(0xe800);
88	mvusb->buf[MVUSB_CMD_PREAMBLE1] = cpu_to_le16(0x0001);
89
90	snprintf(mdio->id, MII_BUS_ID_SIZE, "mvusb-%s", dev_name(dev));
91	mdio->name = mdio->id;
92	mdio->parent = dev;
93	mdio->read = mvusb_mdio_read;
94	mdio->write = mvusb_mdio_write;
95
96	usb_set_intfdata(interface, mvusb);
97	ret = of_mdiobus_register(mdio, dev->of_node);
98	if (ret)
99		goto put_dev;
100
101	return 0;
102
103put_dev:
104	usb_put_dev(mvusb->udev);
105	return ret;
106}
107
108static void mvusb_mdio_disconnect(struct usb_interface *interface)
109{
110	struct mvusb_mdio *mvusb = usb_get_intfdata(interface);
111	struct usb_device *udev = mvusb->udev;
112
113	mdiobus_unregister(mvusb->mdio);
114	usb_set_intfdata(interface, NULL);
115	usb_put_dev(udev);
116}
117
118static struct usb_driver mvusb_mdio_driver = {
119	.name       = "mvusb_mdio",
120	.id_table   = mvusb_mdio_table,
121	.probe      = mvusb_mdio_probe,
122	.disconnect = mvusb_mdio_disconnect,
123};
124
125module_usb_driver(mvusb_mdio_driver);
126
127MODULE_AUTHOR("Tobias Waldekranz <tobias@waldekranz.com>");
128MODULE_DESCRIPTION("Marvell USB MDIO Adapter");
129MODULE_LICENSE("GPL");
130