summaryrefslogtreecommitdiff
path: root/board/keymile/common/keymile_hdlc_enet.c
diff options
context:
space:
mode:
authorGary Jennejohn <garyj@denx.de>2008-11-09 12:36:15 +0100
committerWolfgang Denk <wd@denx.de>2008-12-07 01:33:52 +0100
commit135f5534538bb8ea4f38a7030da12187d22ef7e0 (patch)
tree8cc5fc64e76a3e386b4c3a5cbc299c717b306903 /board/keymile/common/keymile_hdlc_enet.c
parent1cb82a9207a550557399eabc7fe47f21bbd9ddf8 (diff)
downloadu-boot-imx-135f5534538bb8ea4f38a7030da12187d22ef7e0.zip
u-boot-imx-135f5534538bb8ea4f38a7030da12187d22ef7e0.tar.gz
u-boot-imx-135f5534538bb8ea4f38a7030da12187d22ef7e0.tar.bz2
keymile add the common parts of the HDLC driver
This implements the ICN protocol used across the backplane and is needed by all the keymile boards. Signed-off-by: Gary Jennejohn <garyj@denx.de>
Diffstat (limited to 'board/keymile/common/keymile_hdlc_enet.c')
-rw-r--r--board/keymile/common/keymile_hdlc_enet.c620
1 files changed, 620 insertions, 0 deletions
diff --git a/board/keymile/common/keymile_hdlc_enet.c b/board/keymile/common/keymile_hdlc_enet.c
new file mode 100644
index 0000000..141371b
--- /dev/null
+++ b/board/keymile/common/keymile_hdlc_enet.c
@@ -0,0 +1,620 @@
+/*
+ * (C) Copyright 2008
+ * Gary Jennejohn, DENX Software Engineering GmbH, garyj@denx.de.
+ *
+ * Based in part on cpu/mpc8260/ether_scc.c.
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <net.h>
+
+#ifdef CONFIG_KEYMILE_HDLC_ENET
+#ifdef TEST_IT
+#include <command.h>
+#endif
+
+#include "keymile_hdlc_enet.h"
+
+extern char keymile_slot; /* our slot number in the backplane */
+
+/* Allow up to about 50 ms for sending */
+#define TOUT_LOOP 50000
+
+/*
+ * Since, except during initialization, ethact is always HDLC ETHERNET
+ * while we're in the driver, just use serial_printf() everywhere for
+ * output. This avoids possible conflicts when netconsole is being
+ * used.
+ */
+#define dprintf(fmt, args...) serial_printf(fmt, ##args)
+
+/* Cannot use the storage from net.c because we allocate larger buffers */
+static volatile uchar MyPktBuf[HDLC_PKTBUFSRX * PKT_MAXBLR_SIZE + PKTALIGN];
+static volatile uchar *MyRxPackets[HDLC_PKTBUFSRX]; /* Receive packet */
+
+static unsigned int keymile_rxIdx; /* index of the current RX buffer */
+
+static IPaddr_t cachedNumbers[CACHEDNUMBERS]; /* 4 bytes per entry */
+void initCachedNumbers(int);
+
+/*
+ * SCC Ethernet Tx and Rx buffer descriptors allocated at the
+ * immr->udata_bd address on Dual-Port RAM
+ * Provide for Double Buffering
+ */
+typedef volatile struct CommonBufferDescriptor {
+ cbd_t txbd; /* Tx BD */
+ cbd_t rxbd[HDLC_PKTBUFSRX]; /* Rx BD */
+} RTXBD;
+
+/*
+ * This must be extern because it is allocated in DPRAM using CPM-sepcific
+ * code.
+ */
+static RTXBD *rtx;
+
+static int keymile_hdlc_enet_send(struct eth_device *, volatile void *, int);
+static int keymile_hdlc_enet_recv(struct eth_device *);
+void keymile_hdlc_enet_init_bds(RTXBD *);
+extern int keymile_hdlc_enet_init(struct eth_device *, bd_t *);
+extern void keymile_hdlc_enet_halt(struct eth_device *);
+
+/* flags in the buffer descriptor not defined anywhere else */
+#define BD_SC_CT BD_SC_CD
+#define BD_SC_CR 0x04
+#define BD_SC_DE 0x80
+#ifndef BD_SC_TC
+#define BD_SC_TC ((ushort)0x0400) /* Transmit CRC */
+#endif
+#define BD_SC_FIRST BD_SC_TC
+#define BD_SC_STATS (BD_SC_BR | BD_SC_FR | BD_SC_PR | BD_SC_CR | BD_SC_CD \
+ | BD_SC_OV | BD_SC_DE)
+
+#if defined(TEST_RX) || defined(TEST_TX) || defined(TEST_IT)
+static void hexdump(unsigned char *buf, int len)
+{
+ int i;
+ const int bytesPerLine = 32;
+
+ if (len > 4 * bytesPerLine)
+ len = 4 * bytesPerLine;
+ dprintf("\t address: %08x\n", (unsigned int)buf);
+ for (i = 0; i < len; i++) {
+ if (i % bytesPerLine == 0)
+ dprintf("%04x: ", (unsigned short)i);
+ dprintf("%02x ", buf[i]);
+ if ((i + 1) % bytesPerLine == 0) {
+ dprintf("\n");
+ continue;
+ }
+ if ((i + 1) % 8 == 0)
+ printf(" ");
+ }
+ if (len % bytesPerLine)
+ dprintf("\n");
+}
+#endif
+
+int keymile_hdlc_enet_initialize(bd_t *bis)
+{
+ struct eth_device *dev;
+
+ dev = (struct eth_device *) malloc(sizeof *dev);
+ memset(dev, 0, sizeof *dev);
+#ifdef TEST_IT
+ seth = dev;
+#endif
+
+ sprintf(dev->name, "HDLC ETHERNET");
+ dev->init = keymile_hdlc_enet_init;
+ dev->halt = keymile_hdlc_enet_halt;
+ dev->send = keymile_hdlc_enet_send;
+ dev->recv = keymile_hdlc_enet_recv;
+
+ eth_register(dev);
+
+ return 1;
+}
+
+/*
+ * This is called from the board-specific driver after rtx is allocated.
+ */
+void keymile_hdlc_enet_init_bds(RTXBD *board_rtx)
+{
+ volatile cbd_t *bdp;
+ int i;
+
+ rtx = board_rtx;
+ keymile_rxIdx = 0;
+ /*
+ * Initialize the buffer descriptors.
+ */
+ bdp = &rtx->txbd;
+ bdp->cbd_sc = 0;
+ bdp->cbd_bufaddr = 0;
+ bdp->cbd_sc = BD_SC_WRAP;
+
+ /*
+ * Setup RX packet buffers, aligned correctly.
+ * Borrowed from net/net.c.
+ */
+ MyRxPackets[0] = &MyPktBuf[0] + (PKTALIGN - 1);
+ MyRxPackets[0] -= (ulong)MyRxPackets[0] % PKTALIGN;
+ for (i = 1; i < HDLC_PKTBUFSRX; i++)
+ MyRxPackets[i] = MyRxPackets[0] + i * PKT_MAXBLR_SIZE;
+
+ bdp = &rtx->rxbd[0];
+ for (i = 0; i < HDLC_PKTBUFSRX; i++) {
+ bdp->cbd_sc = BD_SC_EMPTY;
+ /* Leave space at the start for INET header. */
+ bdp->cbd_bufaddr = (unsigned int)(MyRxPackets[i] +
+ INET_HDR_ALIGN);
+ bdp++;
+ }
+ bdp--;
+ bdp->cbd_sc |= BD_SC_WRAP;
+}
+
+/*
+ * This returns the current port number for NETCONSOLE. If nc_port
+ * in netconsole.c weren't declared static we wouldn't need this.
+ */
+static short get_netcons_port(void)
+{
+ char *p;
+ short nc_port;
+
+ nc_port = 6666; /* default */
+
+ p = getenv("ncip");
+ if (p != NULL) {
+ p = strchr(p, ':');
+ if (p != NULL)
+ nc_port = simple_strtoul(p + 1, NULL, 10);
+ }
+
+ return htons(nc_port);
+}
+
+/*
+ * Read the port numbers from the variables
+ */
+void initCachedNumbers(int verbose)
+{
+ char *str;
+ ushort port;
+
+ /* already in network order */
+ cachedNumbers[IP_ADDR] = getenv_IPaddr("ipaddr");
+ /* already in network order */
+ cachedNumbers[IP_SERVER] = getenv_IPaddr("serverip");
+ str = getenv("tftpsrcp");
+ if (str != NULL) {
+ /* avoid doing htons() again and again */
+ port = htons((ushort)simple_strtol(str, NULL, 10));
+ cachedNumbers[TFTP_SRC_PORT] = port;
+ } else
+ /* this can never be a valid port number */
+ cachedNumbers[TFTP_SRC_PORT] = (ulong)-1;
+ str = getenv("tftpdstp");
+ if (str != NULL) {
+ /* avoid doing htons() again and again */
+ port = htons((ushort)simple_strtol(str, NULL, 10));
+ cachedNumbers[TFTP_DST_PORT] = port;
+ } else
+ /* this is the default value */
+ cachedNumbers[TFTP_DST_PORT] = htons(WELL_KNOWN_PORT);
+ /* already in network order */
+ cachedNumbers[NETCONS_PORT] = get_netcons_port();
+ if (verbose) {
+ dprintf("\nIP Number Initialization:\n");
+ dprintf(" ip address %08lx\n", cachedNumbers[IP_ADDR]);
+ dprintf(" server ip address %08lx\n",
+ cachedNumbers[IP_SERVER]);
+ dprintf(" tftp client port %ld\n",
+ cachedNumbers[TFTP_SRC_PORT]);
+ dprintf(" tftp server port %ld\n",
+ cachedNumbers[TFTP_DST_PORT]);
+ dprintf(" netcons port %ld\n",
+ cachedNumbers[NETCONS_PORT]);
+ dprintf(" slot number (hex) %02x\n", keymile_slot);
+ }
+}
+
+static void keymile_hdlc_enet_doarp(volatile void *packet, int len)
+{
+ ARP_t *arp;
+ IPaddr_t src_ip; /* U-Boot's IP */
+ IPaddr_t dest_ip; /* the mgcoge's IP */
+ unsigned char *packet_copy = malloc(len);
+
+ /*
+ * Handling an ARP request means that a new transfer has started.
+ * Update our cached parameters now.
+ */
+ initCachedNumbers(0); /* may reinit port numbers */
+
+ /* special handling required for ARP */
+ arp = (ARP_t *)(packet + ETHER_HDR_SIZE);
+ /*
+ * XXXX
+ * This is pretty dirty! NetReceive only uses
+ * a few fields when handling an ARP reply, so
+ * we only modify those here. This could
+ * result in catastrophic failure at a later
+ * time if the handler is modified!
+ */
+ arp->ar_op = htons(ARPOP_REPLY);
+ /* save his/our IP */
+ src_ip = NetReadIP(&arp->ar_data[6]);
+ dest_ip = NetReadIP(&arp->ar_data[16]);
+ /* copy target IP to source IP */
+ NetCopyIP(&arp->ar_data[6], &dest_ip);
+ /* copy our IP to the right place */
+ NetCopyIP(&arp->ar_data[16], &src_ip);
+ /* always use 0x7f as the MAC for the coge */
+ arp->ar_data[0] = HDLC_UACUA;
+ /*
+ * copy the packet
+ * if NetReceive wants to write to stdout, it may overwrite packet
+ * especially if stdout is set to nc!
+ *
+ * However, if the malloc() above fails then we can still try the
+ * original packet, rather than causing the transfer to fail.
+ */
+ if (packet_copy != NULL) {
+ memcpy(packet_copy, (char *)packet, len);
+ NetReceive(packet_copy, len);
+ free(packet_copy);
+ } else
+ NetReceive(packet, len);
+}
+
+/*
+ * NOTE all callers ignore the returned value!
+ * At the moment this only handles ARP Requests, TFTP and NETCONSOLE.
+ */
+static int keymile_hdlc_enet_send(struct eth_device *dev, volatile void *packet,
+ int len)
+{
+ int j;
+ uint data_addr;
+ int data_len;
+ struct icn_hdr header;
+ struct icn_frame *frame;
+ Ethernet_t *et;
+ ARP_t *arp;
+ IP_t *ip;
+
+ if (len > (MAX_FRAME_LENGTH - sizeof(header)))
+ return -1;
+
+ frame = NULL;
+ et = NULL;
+ arp = NULL;
+ ip = NULL;
+
+ j = 0;
+ while ((rtx->txbd.cbd_sc & BD_SC_READY) && (j < TOUT_LOOP)) {
+ /* will also trigger Wd if needed, but maybe too often */
+ udelay(1);
+ j++;
+ }
+ if (j >= TOUT_LOOP) {
+ dprintf("TX not ready sc %x\n", rtx->txbd.cbd_sc);
+ return -1;
+ }
+
+ /*
+ * First check for an ARP Request since this requires special handling.
+ */
+ if (len >= (ARP_HDR_SIZE + ETHER_HDR_SIZE)) {
+ et = (Ethernet_t *)packet;
+ arp = (ARP_t *)(((char *)et) + ETHER_HDR_SIZE);
+ /* ARP and REQUEST? */
+ if (et->et_protlen == PROT_ARP &&
+ arp->ar_op == htons(ARPOP_REQUEST)) {
+ /* just short-circuit the request on the U-Boot side */
+ keymile_hdlc_enet_doarp(packet, len);
+ return 0;
+ }
+ }
+
+ /*
+ * GJ - I suppose the assumption here that len will always be
+ * > INET_HDR_SIZE is alright as long as the network stack
+ * isn't changed.
+ * Do not send INET header.
+ */
+ data_len = len + sizeof(header) - INET_HDR_SIZE;
+ frame = (struct icn_frame *) (((char *)packet) + INET_HDR_SIZE -
+ sizeof(header));
+
+#ifdef TEST_TX
+ printf("frame: %08x, ", frame);
+ hexdump((unsigned char *)packet, data_len + INET_HDR_SIZE);
+#endif
+
+ data_addr = (uint)frame;
+ if (len >= (IP_HDR_SIZE + ETHER_HDR_SIZE))
+ ip = (IP_t *)(packet + ETHER_HDR_SIZE);
+ /* Is it TFTP? TFTP always uses UDP and the cached dport */
+ if (ip != NULL && ip->ip_p == IPPROTO_UDP && ip->udp_dst ==
+ (ushort)cachedNumbers[TFTP_DST_PORT]) {
+ /* just in case the port wasn't set in the environment */
+ if (cachedNumbers[TFTP_SRC_PORT] == (ulong)-1)
+ cachedNumbers[TFTP_SRC_PORT] = ip->udp_src;
+ frame->hdr.application = MGS_TFTP;
+ }
+ /*
+ * Is it NETCONSOLE? NETCONSOLE always uses UDP.
+ */
+ else if (ip != NULL && ip->ip_p == IPPROTO_UDP
+ && ip->udp_dst == (ushort)cachedNumbers[NETCONS_PORT]) {
+ frame->hdr.application = MGS_NETCONS;
+ } else {
+ /* reject unknown packets */
+ /* may do some check on frame->hdr.application */
+ dprintf("Unknown packet type in %s, rejected\n",
+ __func__);
+ return -1;
+ }
+ /*
+ * Could extract the target's slot ID from its MAC here,
+ * but u-boot only wants to talk to the active server.
+ *
+ * avoid setting new source address when moving to another slot
+ */
+ frame->hdr.src_addr = keymile_slot;
+ frame->hdr.dest_addr = HDLC_UACUA;
+#ifdef TEST_TX
+ {
+ dprintf("TX: ");
+ hexdump((unsigned char *)data_addr, data_len);
+ }
+#endif
+
+ flush_cache(data_addr, data_len);
+ rtx->txbd.cbd_bufaddr = data_addr;
+ rtx->txbd.cbd_datlen = data_len;
+ rtx->txbd.cbd_sc |= (BD_SC_READY | BD_SC_TC | BD_SC_LAST | BD_SC_WRAP);
+
+ while ((rtx->txbd.cbd_sc & BD_SC_READY) && (j < TOUT_LOOP)) {
+ /* will also trigger Wd if needed, but maybe too often */
+ udelay(1);
+ j++;
+ }
+ if (j >= TOUT_LOOP)
+ dprintf("TX timeout\n");
+#ifdef ET_DEBUG
+ dprintf("cycles: %d status: %x\n", j, rtx->txbd.cbd_sc);
+#endif
+ j = (rtx->txbd.cbd_sc & BD_SC_STATS); /* return only status bits */
+ return j;
+}
+
+/*
+ * During a receive, the RxIdx points to the current incoming buffer.
+ * When we update through the ring, if the next incoming buffer has
+ * not been given to the system, we just set the empty indicator,
+ * effectively tossing the packet.
+ */
+static int keymile_hdlc_enet_recv(struct eth_device *dev)
+{
+ int length;
+ unsigned char app;
+ struct icn_frame *fp;
+ Ethernet_t *ep;
+ IP_t *ip;
+
+ for (;;) {
+ if (rtx->rxbd[keymile_rxIdx].cbd_sc & BD_SC_EMPTY) {
+ length = -1;
+ break; /* nothing received - leave for() loop */
+ }
+
+ length = rtx->rxbd[keymile_rxIdx].cbd_datlen;
+#ifdef TEST_RX
+ dprintf("packet %d bytes long\n", length);
+#endif
+
+ /*
+ * BD_SC_BR -> LG bit
+ * BD_SC_FR -> NO bit
+ * BD_SC_PR -> AB bit
+ * BD_SC_NAK -> CR bit
+ * 0x80 -> DE bit
+ */
+ if (rtx->rxbd[keymile_rxIdx].cbd_sc & BD_SC_STATS) {
+#ifdef ET_DEBUG
+ dprintf("err: %x\n", rtx->rxbd[keymile_rxIdx].cbd_sc);
+#endif
+ } else if (length > MAX_FRAME_LENGTH) { /* can't happen */
+#ifdef ET_DEBUG
+ dprintf("err: packet too big\n");
+#endif
+ } else {
+ fp = (struct icn_frame *)(MyRxPackets[keymile_rxIdx] +
+ INET_HDR_ALIGN - INET_HDR_SIZE);
+#ifdef TEST_RX
+ dprintf("RX %d: ", keymile_rxIdx);
+ hexdump((unsigned char *)MyRxPackets[keymile_rxIdx],
+ INET_HDR_ALIGN + INET_HDR_SIZE + 4);
+#endif
+ /* copy icn header to the beginning */
+ memcpy(fp, ((char *)fp + INET_HDR_SIZE),
+ sizeof(struct icn_hdr));
+ app = fp->hdr.application;
+ if (app == MGS_NETCONS || app == MGS_TFTP) {
+ struct icn_hdr *ih = &fp->hdr;
+ unsigned char icn_src_addr = ih->src_addr;
+ unsigned char icn_dest_addr = ih->dest_addr;
+
+ /*
+ * expand header by INET_HDR_SIZE
+ */
+ length += INET_HDR_SIZE;
+ /* initalize header */
+ memset((char *)fp->data, 0x00, INET_HDR_SIZE);
+ ep = (Ethernet_t *)fp->data;
+ /* set MACs */
+ ep->et_dest[0] = icn_dest_addr;
+ ep->et_src[0] = icn_src_addr;
+ ep->et_protlen = htons(PROT_IP);
+ /* set ip stuff */
+ ip = (IP_t *)(fp->data + ETHER_HDR_SIZE);
+ /* set ip addresses */
+ ip->ip_src = cachedNumbers[IP_SERVER];
+ ip->ip_dst = cachedNumbers[IP_ADDR];
+ /* ip length */
+ ip->ip_len = htons(length - ETHER_HDR_SIZE -
+ REMOVE);
+ /* ip proto */
+ ip->ip_p = IPPROTO_UDP;
+ switch (app) {
+ case MGS_TFTP:
+ /* swap src/dst port numbers */
+ ip->udp_src = (ushort)
+ cachedNumbers[TFTP_DST_PORT];
+ ip->udp_dst = (ushort)
+ cachedNumbers[TFTP_SRC_PORT];
+ ip->udp_len = ip->ip_len -
+ IP_HDR_SIZE_NO_UDP;
+ ip->udp_xsum = 0;
+ break;
+ case MGS_NETCONS:
+ ip->udp_src = (ushort)
+ cachedNumbers[NETCONS_PORT];
+ /*
+ * in drivers/net/netconsole.c src port
+ * equals dest port
+ */
+ ip->udp_dst = ip->udp_src;
+ ip->udp_len = ip->ip_len -
+ IP_HDR_SIZE_NO_UDP;
+ ip->udp_xsum = 0;
+ break;
+ }
+ /* ip version */
+ ip->ip_hl_v = (0x40) | (0x0f &
+ (IP_HDR_SIZE_NO_UDP / 4));
+ ip->ip_tos = 0;
+ ip->ip_id = 0;
+ /* flags, fragment offset */
+ ip->ip_off = htons(0x4000);
+ ip->ip_ttl = 255; /* time to live */
+ /* have to fixup the checksum */
+ ip->ip_sum = ~NetCksum((uchar *)ip,
+ IP_HDR_SIZE_NO_UDP / 2);
+ /*
+ * Pass the packet up to the protocol layers
+ * but remove dest_addr, src_addr, application
+ * and the CRC.
+ */
+#ifdef TEST_RX
+ hexdump((unsigned char *)fp->data,
+ INET_HDR_SIZE + 4);
+#endif
+ NetReceive(fp->data, length - REMOVE);
+ } else {
+ /*
+ * the other application types are not yet
+ * supported by u-boot.
+ */
+ /* normally drop it */
+#ifdef TEST_NO
+ /* send it anyway */
+ fp = (struct icn_frame *)
+ (MyRxPackets[keymile_rxIdx] +
+ INET_HDR_ALIGN);
+ NetReceive(fp->data, length - REMOVE);
+#endif
+ }
+ }
+
+ /* Give the buffer back to the SCC. */
+ rtx->rxbd[keymile_rxIdx].cbd_datlen = 0;
+
+ /* wrap around buffer index when necessary */
+ if ((keymile_rxIdx + 1) >= HDLC_PKTBUFSRX) {
+ rtx->rxbd[HDLC_PKTBUFSRX - 1].cbd_sc =
+ (BD_SC_WRAP | BD_SC_EMPTY);
+ keymile_rxIdx = 0;
+ } else {
+ rtx->rxbd[keymile_rxIdx].cbd_sc = BD_SC_EMPTY;
+ keymile_rxIdx++;
+ }
+ }
+ return length;
+}
+
+#ifdef TEST_IT
+/* simple send test routine */
+int hdlc_enet_stest(struct cmd_tbl_s *a, int b, int c, char **d)
+{
+ unsigned char pkt[2];
+ int ret;
+
+ dprintf("enter stest\n");
+ /* may have to initialize things */
+ if (seth->state != ETH_STATE_ACTIVE) {
+ /* the bd_t* is not used */
+ if (seth->init(seth, NULL) >= 0)
+ seth->state = ETH_STATE_ACTIVE;
+ }
+ pkt[0] = 0xea;
+ pkt[1] = 0xae;
+ ret = keymile_hdlc_enet_send(seth, pkt, 2);
+ dprintf("return from send %x\n", ret);
+ dprintf("exit stest\n");
+ return ret;
+}
+U_BOOT_CMD(
+ stest, 1, 1, hdlc_enet_stest,
+ "stest - simple send test for hdlc_enet\n",
+ "no arguments\n"
+);
+/* simple receive test routine */
+int hdlc_enet_rtest(struct cmd_tbl_s *a, int b, int c, char **d)
+{
+ int ret;
+
+ dprintf("enter rtest\n");
+ /* may have to initialize things */
+ if (seth->state != ETH_STATE_ACTIVE) {
+ /* the bd_t* is not used */
+ if (seth->init(seth, NULL) >= 0)
+ seth->state = ETH_STATE_ACTIVE;
+ }
+ ret = keymile_hdlc_enet_recv(seth);
+ dprintf("return from recv %x\n", ret);
+ dprintf("exit rtest\n");
+ return ret;
+}
+U_BOOT_CMD(
+ rtest, 1, 1, hdlc_enet_rtest,
+ "rtest - simple receive test for hdlc_enet\n",
+ "no arguments\n"
+);
+#endif
+
+#endif /* CONFIG_KEYMILE_HDLC_ENET */