dm: usb: eth: Add driver-model support to the asix driver

This USB Ethernet driver is quite widely use. Allow it to work with
CONFIG_DM_ETH enabled. Most of the code remains common but there is a new
packet receive flow which is handled specially.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/drivers/usb/eth/asix.c b/drivers/usb/eth/asix.c
index c8697ae..72ec41e 100644
--- a/drivers/usb/eth/asix.c
+++ b/drivers/usb/eth/asix.c
@@ -5,11 +5,11 @@
  */
 
 #include <common.h>
+#include <dm.h>
 #include <usb.h>
+#include <malloc.h>
 #include <linux/mii.h>
 #include "usb_ether.h"
-#include <malloc.h>
-
 
 /* ASIX AX8817X based USB 2.0 Ethernet Devices */
 
@@ -92,14 +92,20 @@
 #define FLAG_TYPE_AX88772B	(1U << 2)
 #define FLAG_EEPROM_MAC		(1U << 3) /* initial mac address in eeprom */
 
-/* local vars */
-static int curr_eth_dev; /* index for name of next device detected */
 
 /* driver private */
 struct asix_private {
 	int flags;
+#ifdef CONFIG_DM_ETH
+	struct ueth_data ueth;
+#endif
 };
 
+#ifndef CONFIG_DM_ETH
+/* local vars */
+static int curr_eth_dev; /* index for name of next device detected */
+#endif
+
 /*
  * Asix infrastructure commands
  */
@@ -284,13 +290,12 @@
 	return ret;
 }
 
-static int asix_write_hwaddr(struct eth_device *eth)
+static int asix_write_hwaddr_common(struct ueth_data *dev, uint8_t *enetaddr)
 {
-	struct ueth_data *dev = (struct ueth_data *)eth->priv;
 	int ret;
 	ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buf, ETH_ALEN);
 
-	memcpy(buf, eth->enetaddr, ETH_ALEN);
+	memcpy(buf, enetaddr, ETH_ALEN);
 
 	ret = asix_write_cmd(dev, AX_CMD_WRITE_NODE_ID, 0, 0, ETH_ALEN, buf);
 	if (ret < 0)
@@ -325,12 +330,11 @@
 	return r;
 }
 
-static int asix_read_mac(struct eth_device *eth)
+static int asix_read_mac_common(struct ueth_data *dev,
+				struct asix_private *priv, uint8_t *enetaddr)
 {
-	struct ueth_data *dev = (struct ueth_data *)eth->priv;
-	struct asix_private *priv = (struct asix_private *)dev->dev_priv;
-	int i;
 	ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buf, ETH_ALEN);
+	int i;
 
 	if (priv->flags & FLAG_EEPROM_MAC) {
 		for (i = 0; i < (ETH_ALEN >> 1); i++) {
@@ -339,7 +343,7 @@
 				debug("Failed to read SROM address 04h.\n");
 				return -1;
 			}
-			memcpy((eth->enetaddr + i * 2), buf, 2);
+			memcpy(enetaddr + i * 2, buf, 2);
 		}
 	} else {
 		if (asix_read_cmd(dev, AX_CMD_READ_NODE_ID, 0, 0, ETH_ALEN, buf)
@@ -347,7 +351,7 @@
 			debug("Failed to read MAC address.\n");
 			return -1;
 		}
-		memcpy(eth->enetaddr, buf, ETH_ALEN);
+		memcpy(enetaddr, buf, ETH_ALEN);
 	}
 
 	return 0;
@@ -414,12 +418,8 @@
 	return 0;
 }
 
-/*
- * Asix callbacks
- */
-static int asix_init(struct eth_device *eth, bd_t *bd)
+static int asix_init_common(struct ueth_data *dev)
 {
-	struct ueth_data	*dev = (struct ueth_data *)eth->priv;
 	int timeout = 0;
 #define TIMEOUT_RESOLUTION 50	/* ms */
 	int link_detected;
@@ -452,9 +452,8 @@
 	return -1;
 }
 
-static int asix_send(struct eth_device *eth, void *packet, int length)
+static int asix_send_common(struct ueth_data *dev, void *packet, int length)
 {
-	struct ueth_data *dev = (struct ueth_data *)eth->priv;
 	int err;
 	u32 packet_len;
 	int actual_len;
@@ -481,6 +480,24 @@
 	return err;
 }
 
+#ifndef CONFIG_DM_ETH
+/*
+ * Asix callbacks
+ */
+static int asix_init(struct eth_device *eth, bd_t *bd)
+{
+	struct ueth_data *dev = (struct ueth_data *)eth->priv;
+
+	return asix_init_common(dev);
+}
+
+static int asix_send(struct eth_device *eth, void *packet, int length)
+{
+	struct ueth_data *dev = (struct ueth_data *)eth->priv;
+
+	return asix_send_common(dev, packet, length);
+}
+
 static int asix_recv(struct eth_device *eth)
 {
 	struct ueth_data *dev = (struct ueth_data *)eth->priv;
@@ -552,6 +569,13 @@
 	debug("** %s()\n", __func__);
 }
 
+static int asix_write_hwaddr(struct eth_device *eth)
+{
+	struct ueth_data *dev = (struct ueth_data *)eth->priv;
+
+	return asix_write_hwaddr_common(dev, eth->enetaddr);
+}
+
 /*
  * Asix probing functions
  */
@@ -694,9 +718,180 @@
 		return 0;
 
 	/* Get the MAC address */
-	if (asix_read_mac(eth))
+	if (asix_read_mac_common(ss, priv, eth->enetaddr))
 		return 0;
 	debug("MAC %pM\n", eth->enetaddr);
 
 	return 1;
 }
+#endif
+
+#ifdef CONFIG_DM_ETH
+static int asix_eth_start(struct udevice *dev)
+{
+	struct asix_private *priv = dev_get_priv(dev);
+
+	return asix_init_common(&priv->ueth);
+}
+
+void asix_eth_stop(struct udevice *dev)
+{
+	debug("** %s()\n", __func__);
+}
+
+int asix_eth_send(struct udevice *dev, void *packet, int length)
+{
+	struct asix_private *priv = dev_get_priv(dev);
+
+	return asix_send_common(&priv->ueth, packet, length);
+}
+
+int asix_eth_recv(struct udevice *dev, int flags, uchar **packetp)
+{
+	struct asix_private *priv = dev_get_priv(dev);
+	struct ueth_data *ueth = &priv->ueth;
+	uint8_t *ptr;
+	int ret, len;
+	u32 packet_len;
+
+	len = usb_ether_get_rx_bytes(ueth, &ptr);
+	debug("%s: first try, len=%d\n", __func__, len);
+	if (!len) {
+		if (!(flags & ETH_RECV_CHECK_DEVICE))
+			return -EAGAIN;
+		ret = usb_ether_receive(ueth, AX_RX_URB_SIZE);
+		if (ret == -EAGAIN)
+			return ret;
+
+		len = usb_ether_get_rx_bytes(ueth, &ptr);
+		debug("%s: second try, len=%d\n", __func__, len);
+	}
+
+	/*
+	 * 1st 4 bytes contain the length of the actual data as two
+	 * complementary 16-bit words. Extract the length of the data.
+	 */
+	if (len < sizeof(packet_len)) {
+		debug("Rx: incomplete packet length\n");
+		goto err;
+	}
+	memcpy(&packet_len, ptr, sizeof(packet_len));
+	le32_to_cpus(&packet_len);
+	if (((~packet_len >> 16) & 0x7ff) != (packet_len & 0x7ff)) {
+		debug("Rx: malformed packet length: %#x (%#x:%#x)\n",
+		      packet_len, (~packet_len >> 16) & 0x7ff,
+		      packet_len & 0x7ff);
+		goto err;
+	}
+	packet_len = packet_len & 0x7ff;
+	if (packet_len > len - sizeof(packet_len)) {
+		debug("Rx: too large packet: %d\n", packet_len);
+		goto err;
+	}
+
+	*packetp = ptr + sizeof(packet_len);
+	return packet_len;
+
+err:
+	usb_ether_advance_rxbuf(ueth, -1);
+	return -EINVAL;
+}
+
+static int asix_free_pkt(struct udevice *dev, uchar *packet, int packet_len)
+{
+	struct asix_private *priv = dev_get_priv(dev);
+
+	if (packet_len & 1)
+		packet_len++;
+	usb_ether_advance_rxbuf(&priv->ueth, sizeof(u32) + packet_len);
+
+	return 0;
+}
+
+int asix_write_hwaddr(struct udevice *dev)
+{
+	struct eth_pdata *pdata = dev_get_platdata(dev);
+	struct asix_private *priv = dev_get_priv(dev);
+
+	if (priv->flags & FLAG_TYPE_AX88172)
+		return -ENOSYS;
+
+	return asix_write_hwaddr_common(&priv->ueth, pdata->enetaddr);
+}
+
+static int asix_eth_probe(struct udevice *dev)
+{
+	struct eth_pdata *pdata = dev_get_platdata(dev);
+	struct asix_private *priv = dev_get_priv(dev);
+	struct ueth_data *ss = &priv->ueth;
+	int ret;
+
+	priv->flags = dev->driver_data;
+	ret = usb_ether_register(dev, ss, AX_RX_URB_SIZE);
+	if (ret)
+		return ret;
+
+	ret = asix_basic_reset(ss);
+	if (ret)
+		goto err;
+
+	/* Get the MAC address */
+	ret = asix_read_mac_common(ss, priv, pdata->enetaddr);
+	if (ret)
+		goto err;
+	debug("MAC %pM\n", pdata->enetaddr);
+
+	return 0;
+
+err:
+	return usb_ether_deregister(ss);
+}
+
+static const struct eth_ops asix_eth_ops = {
+	.start	= asix_eth_start,
+	.send	= asix_eth_send,
+	.recv	= asix_eth_recv,
+	.free_pkt = asix_free_pkt,
+	.stop	= asix_eth_stop,
+	.write_hwaddr = asix_write_hwaddr,
+};
+
+U_BOOT_DRIVER(asix_eth) = {
+	.name	= "asix_eth",
+	.id	= UCLASS_ETH,
+	.probe = asix_eth_probe,
+	.ops	= &asix_eth_ops,
+	.priv_auto_alloc_size = sizeof(struct asix_private),
+	.platdata_auto_alloc_size = sizeof(struct eth_pdata),
+};
+
+static const struct usb_device_id asix_eth_id_table[] = {
+	/* Apple USB Ethernet Adapter */
+	{ USB_DEVICE(0x05ac, 0x1402), .driver_info = FLAG_TYPE_AX88772 },
+	/* D-Link DUB-E100 H/W Ver B1 */
+	{ USB_DEVICE(0x07d1, 0x3c05), .driver_info = FLAG_TYPE_AX88772 },
+	/* D-Link DUB-E100 H/W Ver C1 */
+	{ USB_DEVICE(0x2001, 0x1a02), .driver_info = FLAG_TYPE_AX88772 },
+	/* Cables-to-Go USB Ethernet Adapter */
+	{ USB_DEVICE(0x0b95, 0x772a), .driver_info = FLAG_TYPE_AX88772 },
+	/* Trendnet TU2-ET100 V3.0R */
+	{ USB_DEVICE(0x0b95, 0x7720), .driver_info = FLAG_TYPE_AX88772 },
+	/* SMC */
+	{ USB_DEVICE(0x0b95, 0x1720), .driver_info = FLAG_TYPE_AX88172 },
+	/* MSI - ASIX 88772a */
+	{ USB_DEVICE(0x0db0, 0xa877), .driver_info = FLAG_TYPE_AX88772 },
+	/* Linksys 200M v2.1 */
+	{ USB_DEVICE(0x13b1, 0x0018), .driver_info = FLAG_TYPE_AX88172 },
+	/* 0Q0 cable ethernet */
+	{ USB_DEVICE(0x1557, 0x7720), .driver_info = FLAG_TYPE_AX88772 },
+	/* DLink DUB-E100 H/W Ver B1 Alternate */
+	{ USB_DEVICE(0x2001, 0x3c05), .driver_info = FLAG_TYPE_AX88772 },
+	/* ASIX 88772B */
+	{ USB_DEVICE(0x0b95, 0x772b),
+		.driver_info = FLAG_TYPE_AX88772B | FLAG_EEPROM_MAC },
+	{ USB_DEVICE(0x0b95, 0x7e2b), .driver_info = FLAG_TYPE_AX88772B },
+	{ }		/* Terminating entry */
+};
+
+U_BOOT_USB_DEVICE(asix_eth, asix_eth_id_table);
+#endif