usb4bsd: Bring in FreeBSD's libusbhid, usbhidctl and USB kernel code.
authorSascha Wildner <saw@online.de>
Tue, 25 Sep 2012 21:11:40 +0000 (23:11 +0200)
committerSascha Wildner <saw@online.de>
Thu, 11 Oct 2012 09:27:33 +0000 (11:27 +0200)
In order to make it live peacefully along with our old USB code, name
all directories with new USB code *u4b* instead of *usb*.

This is FreeBSD SVN r231881.

Submitted-by: Markus Pfeiffer <markus.pfeiffer@morphism.de>
199 files changed:
lib/libu4bhid/Makefile [new file with mode: 0644]
lib/libu4bhid/data.c [new file with mode: 0644]
lib/libu4bhid/descr.c [new file with mode: 0644]
lib/libu4bhid/descr_compat.c [new file with mode: 0644]
lib/libu4bhid/parse.c [new file with mode: 0644]
lib/libu4bhid/usage.c [new file with mode: 0644]
lib/libu4bhid/usbhid.3 [new file with mode: 0644]
lib/libu4bhid/usbhid.h [new file with mode: 0644]
lib/libu4bhid/usbvar.h [new file with mode: 0644]
sys/bus/u4b/Makefile [new file with mode: 0644]
sys/bus/u4b/Makefile.usbdevs [new file with mode: 0644]
sys/bus/u4b/controller/at91dci.c [new file with mode: 0644]
sys/bus/u4b/controller/at91dci.h [new file with mode: 0644]
sys/bus/u4b/controller/at91dci_atmelarm.c [new file with mode: 0644]
sys/bus/u4b/controller/atmegadci.c [new file with mode: 0644]
sys/bus/u4b/controller/atmegadci.h [new file with mode: 0644]
sys/bus/u4b/controller/atmegadci_atmelarm.c [new file with mode: 0644]
sys/bus/u4b/controller/avr32dci.c [new file with mode: 0644]
sys/bus/u4b/controller/avr32dci.h [new file with mode: 0644]
sys/bus/u4b/controller/dwc_otg.c [new file with mode: 0644]
sys/bus/u4b/controller/dwc_otg.h [new file with mode: 0644]
sys/bus/u4b/controller/dwc_otg_atmelarm.c [new file with mode: 0644]
sys/bus/u4b/controller/ehci.c [new file with mode: 0644]
sys/bus/u4b/controller/ehci.h [new file with mode: 0644]
sys/bus/u4b/controller/ehci_ixp4xx.c [new file with mode: 0644]
sys/bus/u4b/controller/ehci_mv.c [new file with mode: 0644]
sys/bus/u4b/controller/ehci_pci.c [new file with mode: 0644]
sys/bus/u4b/controller/ehcireg.h [new file with mode: 0644]
sys/bus/u4b/controller/musb_otg.c [new file with mode: 0644]
sys/bus/u4b/controller/musb_otg.h [new file with mode: 0644]
sys/bus/u4b/controller/musb_otg_atmelarm.c [new file with mode: 0644]
sys/bus/u4b/controller/ohci.c [new file with mode: 0644]
sys/bus/u4b/controller/ohci.h [new file with mode: 0644]
sys/bus/u4b/controller/ohci_atmelarm.c [new file with mode: 0644]
sys/bus/u4b/controller/ohci_pci.c [new file with mode: 0644]
sys/bus/u4b/controller/ohci_s3c24x0.c [new file with mode: 0644]
sys/bus/u4b/controller/ohcireg.h [new file with mode: 0644]
sys/bus/u4b/controller/uhci.c [new file with mode: 0644]
sys/bus/u4b/controller/uhci.h [new file with mode: 0644]
sys/bus/u4b/controller/uhci_pci.c [new file with mode: 0644]
sys/bus/u4b/controller/uhcireg.h [new file with mode: 0644]
sys/bus/u4b/controller/usb_controller.c [new file with mode: 0644]
sys/bus/u4b/controller/uss820dci.c [new file with mode: 0644]
sys/bus/u4b/controller/uss820dci.h [new file with mode: 0644]
sys/bus/u4b/controller/uss820dci_atmelarm.c [new file with mode: 0644]
sys/bus/u4b/controller/xhci.c [new file with mode: 0644]
sys/bus/u4b/controller/xhci.h [new file with mode: 0644]
sys/bus/u4b/controller/xhci_pci.c [new file with mode: 0644]
sys/bus/u4b/controller/xhcireg.h [new file with mode: 0644]
sys/bus/u4b/devlist2h.awk [new file with mode: 0644]
sys/bus/u4b/input/atp.c [new file with mode: 0644]
sys/bus/u4b/input/uep.c [new file with mode: 0644]
sys/bus/u4b/input/uhid.c [new file with mode: 0644]
sys/bus/u4b/input/ukbd.c [new file with mode: 0644]
sys/bus/u4b/input/ums.c [new file with mode: 0644]
sys/bus/u4b/input/usb_rdesc.h [new file with mode: 0644]
sys/bus/u4b/misc/udbp.c [new file with mode: 0644]
sys/bus/u4b/misc/udbp.h [new file with mode: 0644]
sys/bus/u4b/misc/ufm.c [new file with mode: 0644]
sys/bus/u4b/net/if_aue.c [new file with mode: 0644]
sys/bus/u4b/net/if_auereg.h [new file with mode: 0644]
sys/bus/u4b/net/if_axe.c [new file with mode: 0644]
sys/bus/u4b/net/if_axereg.h [new file with mode: 0644]
sys/bus/u4b/net/if_cdce.c [new file with mode: 0644]
sys/bus/u4b/net/if_cdcereg.h [new file with mode: 0644]
sys/bus/u4b/net/if_cue.c [new file with mode: 0644]
sys/bus/u4b/net/if_cuereg.h [new file with mode: 0644]
sys/bus/u4b/net/if_ipheth.c [new file with mode: 0644]
sys/bus/u4b/net/if_iphethvar.h [new file with mode: 0644]
sys/bus/u4b/net/if_kue.c [new file with mode: 0644]
sys/bus/u4b/net/if_kuefw.h [new file with mode: 0644]
sys/bus/u4b/net/if_kuereg.h [new file with mode: 0644]
sys/bus/u4b/net/if_mos.c [new file with mode: 0644]
sys/bus/u4b/net/if_mosreg.h [new file with mode: 0644]
sys/bus/u4b/net/if_rue.c [new file with mode: 0644]
sys/bus/u4b/net/if_ruereg.h [new file with mode: 0644]
sys/bus/u4b/net/if_udav.c [new file with mode: 0644]
sys/bus/u4b/net/if_udavreg.h [new file with mode: 0644]
sys/bus/u4b/net/if_usie.c [new file with mode: 0644]
sys/bus/u4b/net/if_usievar.h [new file with mode: 0644]
sys/bus/u4b/net/ruephy.c [new file with mode: 0644]
sys/bus/u4b/net/ruephyreg.h [new file with mode: 0644]
sys/bus/u4b/net/uhso.c [new file with mode: 0644]
sys/bus/u4b/net/usb_ethernet.c [new file with mode: 0644]
sys/bus/u4b/net/usb_ethernet.h [new file with mode: 0644]
sys/bus/u4b/quirk/usb_quirk.c [new file with mode: 0644]
sys/bus/u4b/quirk/usb_quirk.h [new file with mode: 0644]
sys/bus/u4b/serial/u3g.c [new file with mode: 0644]
sys/bus/u4b/serial/uark.c [new file with mode: 0644]
sys/bus/u4b/serial/ubsa.c [new file with mode: 0644]
sys/bus/u4b/serial/ubser.c [new file with mode: 0644]
sys/bus/u4b/serial/uchcom.c [new file with mode: 0644]
sys/bus/u4b/serial/ucycom.c [new file with mode: 0644]
sys/bus/u4b/serial/ufoma.c [new file with mode: 0644]
sys/bus/u4b/serial/uftdi.c [new file with mode: 0644]
sys/bus/u4b/serial/uftdi_reg.h [new file with mode: 0644]
sys/bus/u4b/serial/ugensa.c [new file with mode: 0644]
sys/bus/u4b/serial/uipaq.c [new file with mode: 0644]
sys/bus/u4b/serial/ulpt.c [new file with mode: 0644]
sys/bus/u4b/serial/umcs.c [new file with mode: 0644]
sys/bus/u4b/serial/umcs.h [new file with mode: 0644]
sys/bus/u4b/serial/umct.c [new file with mode: 0644]
sys/bus/u4b/serial/umodem.c [new file with mode: 0644]
sys/bus/u4b/serial/umoscom.c [new file with mode: 0644]
sys/bus/u4b/serial/uplcom.c [new file with mode: 0644]
sys/bus/u4b/serial/usb_serial.c [new file with mode: 0644]
sys/bus/u4b/serial/usb_serial.h [new file with mode: 0644]
sys/bus/u4b/serial/uslcom.c [new file with mode: 0644]
sys/bus/u4b/serial/uvisor.c [new file with mode: 0644]
sys/bus/u4b/serial/uvscom.c [new file with mode: 0644]
sys/bus/u4b/storage/rio500_usb.h [new file with mode: 0644]
sys/bus/u4b/storage/umass.c [new file with mode: 0644]
sys/bus/u4b/storage/urio.c [new file with mode: 0644]
sys/bus/u4b/storage/ustorage_fs.c [new file with mode: 0644]
sys/bus/u4b/template/usb_template.c [new file with mode: 0644]
sys/bus/u4b/template/usb_template.h [new file with mode: 0644]
sys/bus/u4b/template/usb_template_audio.c [new file with mode: 0644]
sys/bus/u4b/template/usb_template_cdce.c [new file with mode: 0644]
sys/bus/u4b/template/usb_template_kbd.c [new file with mode: 0644]
sys/bus/u4b/template/usb_template_modem.c [new file with mode: 0644]
sys/bus/u4b/template/usb_template_mouse.c [new file with mode: 0644]
sys/bus/u4b/template/usb_template_msc.c [new file with mode: 0644]
sys/bus/u4b/template/usb_template_mtp.c [new file with mode: 0644]
sys/bus/u4b/ufm_ioctl.h [new file with mode: 0644]
sys/bus/u4b/usb.h [new file with mode: 0644]
sys/bus/u4b/usb/Makefile [new file with mode: 0644]
sys/bus/u4b/usb_bus.h [new file with mode: 0644]
sys/bus/u4b/usb_busdma.c [new file with mode: 0644]
sys/bus/u4b/usb_busdma.h [new file with mode: 0644]
sys/bus/u4b/usb_cdc.h [new file with mode: 0644]
sys/bus/u4b/usb_compat_linux.c [new file with mode: 0644]
sys/bus/u4b/usb_compat_linux.h [new file with mode: 0644]
sys/bus/u4b/usb_controller.h [new file with mode: 0644]
sys/bus/u4b/usb_core.c [new file with mode: 0644]
sys/bus/u4b/usb_core.h [new file with mode: 0644]
sys/bus/u4b/usb_debug.c [new file with mode: 0644]
sys/bus/u4b/usb_debug.h [new file with mode: 0644]
sys/bus/u4b/usb_dev.c [new file with mode: 0644]
sys/bus/u4b/usb_dev.h [new file with mode: 0644]
sys/bus/u4b/usb_device.c [new file with mode: 0644]
sys/bus/u4b/usb_device.h [new file with mode: 0644]
sys/bus/u4b/usb_dynamic.c [new file with mode: 0644]
sys/bus/u4b/usb_dynamic.h [new file with mode: 0644]
sys/bus/u4b/usb_endian.h [new file with mode: 0644]
sys/bus/u4b/usb_error.c [new file with mode: 0644]
sys/bus/u4b/usb_freebsd.h [new file with mode: 0644]
sys/bus/u4b/usb_generic.c [new file with mode: 0644]
sys/bus/u4b/usb_generic.h [new file with mode: 0644]
sys/bus/u4b/usb_handle_request.c [new file with mode: 0644]
sys/bus/u4b/usb_hid.c [new file with mode: 0644]
sys/bus/u4b/usb_hub.c [new file with mode: 0644]
sys/bus/u4b/usb_hub.h [new file with mode: 0644]
sys/bus/u4b/usb_if.m [new file with mode: 0644]
sys/bus/u4b/usb_ioctl.h [new file with mode: 0644]
sys/bus/u4b/usb_lookup.c [new file with mode: 0644]
sys/bus/u4b/usb_mbuf.c [new file with mode: 0644]
sys/bus/u4b/usb_mbuf.h [new file with mode: 0644]
sys/bus/u4b/usb_msctest.c [new file with mode: 0644]
sys/bus/u4b/usb_msctest.h [new file with mode: 0644]
sys/bus/u4b/usb_parse.c [new file with mode: 0644]
sys/bus/u4b/usb_pci.h [new file with mode: 0644]
sys/bus/u4b/usb_pf.c [new file with mode: 0644]
sys/bus/u4b/usb_pf.h [new file with mode: 0644]
sys/bus/u4b/usb_process.c [new file with mode: 0644]
sys/bus/u4b/usb_process.h [new file with mode: 0644]
sys/bus/u4b/usb_request.c [new file with mode: 0644]
sys/bus/u4b/usb_request.h [new file with mode: 0644]
sys/bus/u4b/usb_transfer.c [new file with mode: 0644]
sys/bus/u4b/usb_transfer.h [new file with mode: 0644]
sys/bus/u4b/usb_util.c [new file with mode: 0644]
sys/bus/u4b/usb_util.h [new file with mode: 0644]
sys/bus/u4b/usbdevs [new file with mode: 0644]
sys/bus/u4b/usbdi.h [new file with mode: 0644]
sys/bus/u4b/usbdi_util.h [new file with mode: 0644]
sys/bus/u4b/usbhid.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_rum.c [new file with mode: 0644]
sys/bus/u4b/wlan/if_rumfw.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_rumreg.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_rumvar.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_run.c [new file with mode: 0644]
sys/bus/u4b/wlan/if_runreg.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_runvar.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_uath.c [new file with mode: 0644]
sys/bus/u4b/wlan/if_uathreg.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_uathvar.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_upgt.c [new file with mode: 0644]
sys/bus/u4b/wlan/if_upgtvar.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_ural.c [new file with mode: 0644]
sys/bus/u4b/wlan/if_uralreg.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_uralvar.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_urtw.c [new file with mode: 0644]
sys/bus/u4b/wlan/if_urtwreg.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_urtwvar.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_zyd.c [new file with mode: 0644]
sys/bus/u4b/wlan/if_zydfw.h [new file with mode: 0644]
sys/bus/u4b/wlan/if_zydreg.h [new file with mode: 0644]
usr.bin/u4bhidctl/Makefile [new file with mode: 0644]
usr.bin/u4bhidctl/usbhid.c [new file with mode: 0644]
usr.bin/u4bhidctl/usbhidctl.1 [new file with mode: 0644]

diff --git a/lib/libu4bhid/Makefile b/lib/libu4bhid/Makefile
new file mode 100644 (file)
index 0000000..7dba7ff
--- /dev/null
@@ -0,0 +1,26 @@
+#      $NetBSD: Makefile,v 1.5 1999/07/23 09:44:38 mrg Exp $
+#      $FreeBSD$
+
+LIB=   usbhid
+MAN=   usbhid.3
+
+SHLIB_MAJOR= 4
+
+MLINKS=        usbhid.3 libusbhid.3 usbhid.3 hid_get_report_desc.3 \
+       usbhid.3 hid_dispose_report_desc.3 \
+       usbhid.3 hid_start_parse.3 usbhid.3 hid_end_parse.3 \
+       usbhid.3 hid_get_item.3 usbhid.3 hid_report_size.3 \
+       usbhid.3 hid_locate.3 \
+       usbhid.3 hid_usage_page.3 usbhid.3 hid_usage_in_page.3 \
+       usbhid.3 hid_init.3 \
+       usbhid.3 hid_get_data.3 usbhid.3 hid_set_data.3
+
+SRCS=  descr.c descr_compat.c parse.c usage.c data.c
+
+INCS=  usbhid.h
+
+.if defined(COMPAT_32BIT)
+CFLAGS+=       -DCOMPAT_32BIT
+.endif
+
+.include <bsd.lib.mk>
diff --git a/lib/libu4bhid/data.c b/lib/libu4bhid/data.c
new file mode 100644 (file)
index 0000000..f607737
--- /dev/null
@@ -0,0 +1,143 @@
+/*     $NetBSD: data.c,v 1.8 2000/04/02 11:10:53 augustss Exp $        */
+
+/*
+ * Copyright (c) 1999 Lennart Augustsson <augustss@netbsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dev/usb/usb_ioctl.h>
+#include "usbhid.h"
+#include "usbvar.h"
+
+int32_t
+hid_get_data(const void *p, const hid_item_t *h)
+{
+       const uint8_t *buf;
+       uint32_t hpos;
+       uint32_t hsize;
+       uint32_t data;
+       int i, end, offs;
+
+       buf = p;
+
+       /* Skip report ID byte. */
+       if (h->report_ID > 0)
+               buf++;
+
+       hpos = h->pos;                  /* bit position of data */
+       hsize = h->report_size;         /* bit length of data */
+
+       /* Range check and limit */
+       if (hsize == 0)
+               return (0);
+       if (hsize > 32)
+               hsize = 32;
+
+       offs = hpos / 8;
+       end = (hpos + hsize) / 8 - offs;
+       data = 0;
+       for (i = 0; i <= end; i++)
+               data |= buf[offs + i] << (i*8);
+
+       /* Correctly shift down data */
+       data >>= hpos % 8;
+       hsize = 32 - hsize;
+
+       /* Mask and sign extend in one */
+       if ((h->logical_minimum < 0) || (h->logical_maximum < 0))
+               data = (int32_t)((int32_t)data << hsize) >> hsize;
+       else
+               data = (uint32_t)((uint32_t)data << hsize) >> hsize;
+
+       return (data);
+}
+
+void
+hid_set_data(void *p, const hid_item_t *h, int32_t data)
+{
+       uint8_t *buf;
+       uint32_t hpos;
+       uint32_t hsize;
+       uint32_t mask;
+       int i;
+       int end;
+       int offs;
+
+       buf = p;
+
+       /* Set report ID byte. */
+       if (h->report_ID > 0)
+               *buf++ = h->report_ID & 0xff;
+
+       hpos = h->pos;                  /* bit position of data */
+       hsize = h->report_size;         /* bit length of data */
+
+       if (hsize != 32) {
+               mask = (1 << hsize) - 1;
+               data &= mask;
+       } else
+               mask = ~0;
+
+       data <<= (hpos % 8);
+       mask <<= (hpos % 8);
+       mask = ~mask;
+
+       offs = hpos / 8;
+       end = (hpos + hsize) / 8 - offs;
+
+       for (i = 0; i <= end; i++)
+               buf[offs + i] = (buf[offs + i] & (mask >> (i*8))) |
+                   ((data >> (i*8)) & 0xff);
+}
+
+int
+hid_get_report(int fd, enum hid_kind k, unsigned char *data, unsigned int size)
+{
+       struct usb_gen_descriptor ugd;
+
+       memset(&ugd, 0, sizeof(ugd));
+       ugd.ugd_data = hid_pass_ptr(data);
+       ugd.ugd_maxlen = size;
+       ugd.ugd_report_type = k + 1;
+       return (ioctl(fd, USB_GET_REPORT, &ugd));
+}
+
+int
+hid_set_report(int fd, enum hid_kind k, unsigned char *data, unsigned int size)
+{
+       struct usb_gen_descriptor ugd;
+
+       memset(&ugd, 0, sizeof(ugd));
+       ugd.ugd_data = hid_pass_ptr(data);
+       ugd.ugd_maxlen = size;
+       ugd.ugd_report_type = k + 1;
+       return (ioctl(fd, USB_SET_REPORT, &ugd));
+}
diff --git a/lib/libu4bhid/descr.c b/lib/libu4bhid/descr.c
new file mode 100644 (file)
index 0000000..def90da
--- /dev/null
@@ -0,0 +1,176 @@
+/*     $NetBSD: descr.c,v 1.9 2000/09/24 02:13:24 augustss Exp $       */
+
+/*
+ * Copyright (c) 1999 Lennart Augustsson <augustss@netbsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <dev/usb/usb_ioctl.h>
+
+#include "usbhid.h"
+#include "usbvar.h"
+
+int
+hid_set_immed(int fd, int enable)
+{
+       int ret;
+       ret = ioctl(fd, USB_SET_IMMED, &enable);
+#ifdef HID_COMPAT7
+       if (ret < 0)
+               ret = hid_set_immed_compat7(fd, enable);
+#endif
+       return (ret);
+}
+
+int
+hid_get_report_id(int fd)
+{
+       report_desc_t rep;
+       hid_data_t d;
+       hid_item_t h;
+       int kindset;
+       int temp = -1;
+       int ret;
+
+       if ((rep = hid_get_report_desc(fd)) == NULL)
+               goto use_ioctl;
+       kindset = 1 << hid_input | 1 << hid_output | 1 << hid_feature;
+       for (d = hid_start_parse(rep, kindset, 0); hid_get_item(d, &h); ) {
+               /* Return the first report ID we met. */
+               if (h.report_ID != 0) {
+                       temp = h.report_ID;
+                       break;
+               }
+       }
+       hid_end_parse(d);
+       hid_dispose_report_desc(rep);
+
+       if (temp > 0)
+               return (temp);
+
+use_ioctl:
+       ret = ioctl(fd, USB_GET_REPORT_ID, &temp);
+#ifdef HID_COMPAT7
+       if (ret < 0)
+               ret = hid_get_report_id_compat7(fd);
+       else
+#endif
+               ret = temp;
+
+       return (ret);
+}
+
+report_desc_t
+hid_get_report_desc(int fd)
+{
+       struct usb_gen_descriptor ugd;
+       report_desc_t rep;
+       void *data;
+
+       memset(&ugd, 0, sizeof(ugd));
+
+       /* get actual length first */
+       ugd.ugd_data = hid_pass_ptr(NULL);
+       ugd.ugd_maxlen = 65535;
+       if (ioctl(fd, USB_GET_REPORT_DESC, &ugd) < 0) {
+#ifdef HID_COMPAT7
+               /* could not read descriptor */
+               /* try FreeBSD 7 compat code */
+               return (hid_get_report_desc_compat7(fd));
+#else
+               return (NULL);
+#endif
+       }
+
+       /*
+        * NOTE: The kernel will return a failure if 
+        * "ugd_actlen" is zero.
+        */
+       data = malloc(ugd.ugd_actlen);
+       if (data == NULL)
+               return (NULL);
+
+       /* fetch actual descriptor */
+       ugd.ugd_data = hid_pass_ptr(data);
+       ugd.ugd_maxlen = ugd.ugd_actlen;
+       if (ioctl(fd, USB_GET_REPORT_DESC, &ugd) < 0) {
+               /* could not read descriptor */
+               free(data);
+               return (NULL);
+       }
+
+       /* sanity check */
+       if (ugd.ugd_actlen < 1) {
+               /* invalid report descriptor */
+               free(data);
+               return (NULL);
+       }
+
+       /* check END_COLLECTION */
+       if (((unsigned char *)data)[ugd.ugd_actlen -1] != 0xC0) {
+               /* invalid end byte */
+               free(data);
+               return (NULL);
+       }
+
+       rep = hid_use_report_desc(data, ugd.ugd_actlen);
+
+       free(data);
+
+       return (rep);
+}
+
+report_desc_t
+hid_use_report_desc(unsigned char *data, unsigned int size)
+{
+       report_desc_t r;
+
+       r = malloc(sizeof(*r) + size);
+       if (r == 0) {
+               errno = ENOMEM;
+               return (NULL);
+       }
+       r->size = size;
+       memcpy(r->data, data, size);
+       return (r);
+}
+
+void
+hid_dispose_report_desc(report_desc_t r)
+{
+
+       free(r);
+}
diff --git a/lib/libu4bhid/descr_compat.c b/lib/libu4bhid/descr_compat.c
new file mode 100644 (file)
index 0000000..a38d8d7
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 1999 Lennart Augustsson <augustss@netbsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains fallback-compatibility code for the old FreeBSD
+ * USB stack.
+ */
+#ifdef HID_COMPAT7
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+
+#include <dev/usb/usb.h>
+
+#include "usbhid.h"
+#include "usbvar.h"
+
+int
+hid_set_immed_compat7(int fd, int enable)
+{
+       return (ioctl(fd, USB_SET_IMMED, &enable));
+}
+
+int
+hid_get_report_id_compat7(int fd)
+{
+       int temp = -1;
+
+       if (ioctl(fd, USB_GET_REPORT_ID, &temp) < 0)
+               return (-1);
+
+       return (temp);
+}
+
+report_desc_t
+hid_get_report_desc_compat7(int fd)
+{
+       struct usb_ctl_report_desc rep;
+
+       rep.ucrd_size = 0;
+       if (ioctl(fd, USB_GET_REPORT_DESC, &rep) < 0)
+               return (NULL);
+
+       return (hid_use_report_desc(rep.ucrd_data, (unsigned int)rep.ucrd_size));
+}
+#endif /* HID_COMPAT7 */
diff --git a/lib/libu4bhid/parse.c b/lib/libu4bhid/parse.c
new file mode 100644 (file)
index 0000000..f7c2cb1
--- /dev/null
@@ -0,0 +1,566 @@
+/*     $NetBSD: parse.c,v 1.11 2000/09/24 02:19:54 augustss Exp $      */
+
+/*
+ * Copyright (c) 1999, 2001 Lennart Augustsson <augustss@netbsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+
+#include "usbhid.h"
+#include "usbvar.h"
+
+#define        MAXUSAGE 100
+#define        MAXPUSH 4
+#define        MAXID 64
+#define        ITEMTYPES 3
+
+struct hid_pos_data {
+       int32_t rid;
+       uint32_t pos[ITEMTYPES];
+};
+
+struct hid_data {
+       const uint8_t *start;
+       const uint8_t *end;
+       const uint8_t *p;
+       struct hid_item cur[MAXPUSH];
+       struct hid_pos_data last_pos[MAXID];
+       uint32_t pos[ITEMTYPES];
+       int32_t usages_min[MAXUSAGE];
+       int32_t usages_max[MAXUSAGE];
+       int32_t usage_last;     /* last seen usage */
+       uint32_t loc_size;      /* last seen size */
+       uint32_t loc_count;     /* last seen count */
+       uint8_t kindset;        /* we have 5 kinds so 8 bits are enough */
+       uint8_t pushlevel;      /* current pushlevel */
+       uint8_t ncount;         /* end usage item count */
+       uint8_t icount;         /* current usage item count */
+       uint8_t nusage;         /* end "usages_min/max" index */
+       uint8_t iusage;         /* current "usages_min/max" index */
+       uint8_t ousage;         /* current "usages_min/max" offset */
+       uint8_t susage;         /* usage set flags */
+};
+
+/*------------------------------------------------------------------------*
+ *     hid_clear_local
+ *------------------------------------------------------------------------*/
+static void
+hid_clear_local(hid_item_t *c)
+{
+
+       c->usage = 0;
+       c->usage_minimum = 0;
+       c->usage_maximum = 0;
+       c->designator_index = 0;
+       c->designator_minimum = 0;
+       c->designator_maximum = 0;
+       c->string_index = 0;
+       c->string_minimum = 0;
+       c->string_maximum = 0;
+       c->set_delimiter = 0;
+}
+
+static void
+hid_switch_rid(struct hid_data *s, struct hid_item *c, int32_t next_rID)
+{
+       uint8_t i, j;
+
+       /* check for same report ID - optimise */
+
+       if (c->report_ID == next_rID)
+               return;
+
+       /* save current position for current rID */
+
+       if (c->report_ID == 0) {
+               i = 0;
+       } else {
+               for (i = 1; i != MAXID; i++) {
+                       if (s->last_pos[i].rid == c->report_ID)
+                               break;
+                       if (s->last_pos[i].rid == 0)
+                               break;
+               }
+       }
+       if (i != MAXID) {
+               s->last_pos[i].rid = c->report_ID;
+               for (j = 0; j < ITEMTYPES; j++)
+                       s->last_pos[i].pos[j] = s->pos[j];
+       }
+
+       /* store next report ID */
+
+       c->report_ID = next_rID;
+
+       /* lookup last position for next rID */
+
+       if (next_rID == 0) {
+               i = 0;
+       } else {
+               for (i = 1; i != MAXID; i++) {
+                       if (s->last_pos[i].rid == next_rID)
+                               break;
+                       if (s->last_pos[i].rid == 0)
+                               break;
+               }
+       }
+       if (i != MAXID) {
+               s->last_pos[i].rid = next_rID;
+               for (j = 0; j < ITEMTYPES; j++)
+                       s->pos[j] = s->last_pos[i].pos[j];
+       } else {
+               for (j = 0; j < ITEMTYPES; j++)
+                       s->pos[j] = 0;  /* Out of RID entries. */
+       }
+}
+
+/*------------------------------------------------------------------------*
+ *     hid_start_parse
+ *------------------------------------------------------------------------*/
+hid_data_t
+hid_start_parse(report_desc_t d, int kindset, int id __unused)
+{
+       struct hid_data *s;
+
+       s = malloc(sizeof *s);
+       memset(s, 0, sizeof *s);
+       s->start = s->p = d->data;
+       s->end = d->data + d->size;
+       s->kindset = kindset;
+       return (s);
+}
+
+/*------------------------------------------------------------------------*
+ *     hid_end_parse
+ *------------------------------------------------------------------------*/
+void
+hid_end_parse(hid_data_t s)
+{
+
+       if (s == NULL)
+               return;
+
+       free(s);
+}
+
+/*------------------------------------------------------------------------*
+ *     get byte from HID descriptor
+ *------------------------------------------------------------------------*/
+static uint8_t
+hid_get_byte(struct hid_data *s, const uint16_t wSize)
+{
+       const uint8_t *ptr;
+       uint8_t retval;
+
+       ptr = s->p;
+
+       /* check if end is reached */
+       if (ptr == s->end)
+               return (0);
+
+       /* read out a byte */
+       retval = *ptr;
+
+       /* check if data pointer can be advanced by "wSize" bytes */
+       if ((s->end - ptr) < wSize)
+               ptr = s->end;
+       else
+               ptr += wSize;
+
+       /* update pointer */
+       s->p = ptr;
+
+       return (retval);
+}
+
+/*------------------------------------------------------------------------*
+ *     hid_get_item
+ *------------------------------------------------------------------------*/
+int
+hid_get_item(hid_data_t s, hid_item_t *h)
+{
+       hid_item_t *c;
+       unsigned int bTag, bType, bSize;
+       int32_t mask;
+       int32_t dval;
+
+       if (s == NULL)
+               return (0);
+
+       c = &s->cur[s->pushlevel];
+
+ top:
+       /* check if there is an array of items */
+       if (s->icount < s->ncount) {
+               /* get current usage */
+               if (s->iusage < s->nusage) {
+                       dval = s->usages_min[s->iusage] + s->ousage;
+                       c->usage = dval;
+                       s->usage_last = dval;
+                       if (dval == s->usages_max[s->iusage]) {
+                               s->iusage ++;
+                               s->ousage = 0;
+                       } else {
+                               s->ousage ++;
+                       }
+               } else {
+                       /* Using last usage */
+                       dval = s->usage_last;
+               }
+               s->icount ++;
+               /* 
+                * Only copy HID item, increment position and return
+                * if correct kindset!
+                */
+               if (s->kindset & (1 << c->kind)) {
+                       *h = *c;
+                       h->pos = s->pos[c->kind];
+                       s->pos[c->kind] += c->report_size * c->report_count;
+                       return (1);
+               }
+       }
+
+       /* reset state variables */
+       s->icount = 0;
+       s->ncount = 0;
+       s->iusage = 0;
+       s->nusage = 0;
+       s->susage = 0;
+       s->ousage = 0;
+       hid_clear_local(c);
+
+       /* get next item */
+       while (s->p != s->end) {
+
+               bSize = hid_get_byte(s, 1);
+               if (bSize == 0xfe) {
+                       /* long item */
+                       bSize = hid_get_byte(s, 1);
+                       bSize |= hid_get_byte(s, 1) << 8;
+                       bTag = hid_get_byte(s, 1);
+                       bType = 0xff;   /* XXX what should it be */
+               } else {
+                       /* short item */
+                       bTag = bSize >> 4;
+                       bType = (bSize >> 2) & 3;
+                       bSize &= 3;
+                       if (bSize == 3)
+                               bSize = 4;
+               }
+
+               switch(bSize) {
+               case 0:
+                       dval = 0;
+                       mask = 0;
+                       break;
+               case 1:
+                       dval = (int8_t)hid_get_byte(s, 1);
+                       mask = 0xFF;
+                       break;
+               case 2:
+                       dval = hid_get_byte(s, 1);
+                       dval |= hid_get_byte(s, 1) << 8;
+                       dval = (int16_t)dval;
+                       mask = 0xFFFF;
+                       break;
+               case 4:
+                       dval = hid_get_byte(s, 1);
+                       dval |= hid_get_byte(s, 1) << 8;
+                       dval |= hid_get_byte(s, 1) << 16;
+                       dval |= hid_get_byte(s, 1) << 24;
+                       mask = 0xFFFFFFFF;
+                       break;
+               default:
+                       dval = hid_get_byte(s, bSize);
+                       continue;
+               }
+
+               switch (bType) {
+               case 0:         /* Main */
+                       switch (bTag) {
+                       case 8: /* Input */
+                               c->kind = hid_input;
+                               c->flags = dval;
+               ret:
+                               c->report_count = s->loc_count;
+                               c->report_size = s->loc_size;
+
+                               if (c->flags & HIO_VARIABLE) {
+                                       /* range check usage count */
+                                       if (c->report_count > 255) {
+                                               s->ncount = 255;
+                                       } else
+                                               s->ncount = c->report_count;
+
+                                       /* 
+                                        * The "top" loop will return
+                                        * one and one item:
+                                        */
+                                       c->report_count = 1;
+                                       c->usage_minimum = 0;
+                                       c->usage_maximum = 0;
+                               } else {
+                                       s->ncount = 1;
+                               }
+                               goto top;
+
+                       case 9: /* Output */
+                               c->kind = hid_output;
+                               c->flags = dval;
+                               goto ret;
+                       case 10:        /* Collection */
+                               c->kind = hid_collection;
+                               c->collection = dval;
+                               c->collevel++;
+                               c->usage = s->usage_last;
+                               *h = *c;
+                               return (1);
+                       case 11:        /* Feature */
+                               c->kind = hid_feature;
+                               c->flags = dval;
+                               goto ret;
+                       case 12:        /* End collection */
+                               c->kind = hid_endcollection;
+                               if (c->collevel == 0) {
+                                       /* Invalid end collection. */
+                                       return (0);
+                               }
+                               c->collevel--;
+                               *h = *c;
+                               return (1);
+                       default:
+                               break;
+                       }
+                       break;
+
+               case 1:         /* Global */
+                       switch (bTag) {
+                       case 0:
+                               c->_usage_page = dval << 16;
+                               break;
+                       case 1:
+                               c->logical_minimum = dval;
+                               break;
+                       case 2:
+                               c->logical_maximum = dval;
+                               break;
+                       case 3:
+                               c->physical_minimum = dval;
+                               break;
+                       case 4:
+                               c->physical_maximum = dval;
+                               break;
+                       case 5:
+                               c->unit_exponent = dval;
+                               break;
+                       case 6:
+                               c->unit = dval;
+                               break;
+                       case 7:
+                               /* mask because value is unsigned */
+                               s->loc_size = dval & mask;
+                               break;
+                       case 8:
+                               hid_switch_rid(s, c, dval);
+                               break;
+                       case 9:
+                               /* mask because value is unsigned */
+                               s->loc_count = dval & mask;
+                               break;
+                       case 10:        /* Push */
+                               s->pushlevel ++;
+                               if (s->pushlevel < MAXPUSH) {
+                                       s->cur[s->pushlevel] = *c;
+                                       /* store size and count */
+                                       c->report_size = s->loc_size;
+                                       c->report_count = s->loc_count;
+                                       /* update current item pointer */
+                                       c = &s->cur[s->pushlevel];
+                               }
+                               break;
+                       case 11:        /* Pop */
+                               s->pushlevel --;
+                               if (s->pushlevel < MAXPUSH) {
+                                       c = &s->cur[s->pushlevel];
+                                       /* restore size and count */
+                                       s->loc_size = c->report_size;
+                                       s->loc_count = c->report_count;
+                                       c->report_size = 0;
+                                       c->report_count = 0;
+                               }
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+               case 2:         /* Local */
+                       switch (bTag) {
+                       case 0:
+                               if (bSize != 4)
+                                       dval = (dval & mask) | c->_usage_page;
+
+                               /* set last usage, in case of a collection */
+                               s->usage_last = dval;
+
+                               if (s->nusage < MAXUSAGE) {
+                                       s->usages_min[s->nusage] = dval;
+                                       s->usages_max[s->nusage] = dval;
+                                       s->nusage ++;
+                               }
+                               /* else XXX */
+
+                               /* clear any pending usage sets */
+                               s->susage = 0;
+                               break;
+                       case 1:
+                               s->susage |= 1;
+
+                               if (bSize != 4)
+                                       dval = (dval & mask) | c->_usage_page;
+                               c->usage_minimum = dval;
+
+                               goto check_set;
+                       case 2:
+                               s->susage |= 2;
+
+                               if (bSize != 4)
+                                       dval = (dval & mask) | c->_usage_page;
+                               c->usage_maximum = dval;
+
+                       check_set:
+                               if (s->susage != 3)
+                                       break;
+
+                               /* sanity check */
+                               if ((s->nusage < MAXUSAGE) &&
+                                   (c->usage_minimum <= c->usage_maximum)) {
+                                       /* add usage range */
+                                       s->usages_min[s->nusage] = 
+                                           c->usage_minimum;
+                                       s->usages_max[s->nusage] = 
+                                           c->usage_maximum;
+                                       s->nusage ++;
+                               }
+                               /* else XXX */
+
+                               s->susage = 0;
+                               break;
+                       case 3:
+                               c->designator_index = dval;
+                               break;
+                       case 4:
+                               c->designator_minimum = dval;
+                               break;
+                       case 5:
+                               c->designator_maximum = dval;
+                               break;
+                       case 7:
+                               c->string_index = dval;
+                               break;
+                       case 8:
+                               c->string_minimum = dval;
+                               break;
+                       case 9:
+                               c->string_maximum = dval;
+                               break;
+                       case 10:
+                               c->set_delimiter = dval;
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+       return (0);
+}
+
+int
+hid_report_size(report_desc_t r, enum hid_kind k, int id)
+{
+       struct hid_data *d;
+       struct hid_item h;
+       uint32_t temp;
+       uint32_t hpos;
+       uint32_t lpos;
+       int report_id = 0;
+
+       hpos = 0;
+       lpos = 0xFFFFFFFF;
+
+       memset(&h, 0, sizeof h);
+       for (d = hid_start_parse(r, 1 << k, id); hid_get_item(d, &h); ) {
+               if ((h.report_ID == id || id < 0) && h.kind == k) {
+                       /* compute minimum */
+                       if (lpos > h.pos)
+                               lpos = h.pos;
+                       /* compute end position */
+                       temp = h.pos + (h.report_size * h.report_count);
+                       /* compute maximum */
+                       if (hpos < temp)
+                               hpos = temp;
+                       if (h.report_ID != 0)
+                               report_id = 1;
+               }
+       }
+       hid_end_parse(d);
+
+       /* safety check - can happen in case of currupt descriptors */
+       if (lpos > hpos)
+               temp = 0;
+       else
+               temp = hpos - lpos;
+
+       /* return length in bytes rounded up */
+       return ((temp + 7) / 8 + report_id);
+}
+
+int
+hid_locate(report_desc_t desc, unsigned int u, enum hid_kind k,
+          hid_item_t *h, int id)
+{
+       struct hid_data *d;
+
+       for (d = hid_start_parse(desc, 1 << k, id); hid_get_item(d, h); ) {
+               if (h->kind == k && !(h->flags & HIO_CONST) && h->usage == u) {
+                       hid_end_parse(d);
+                       return (1);
+               }
+       }
+       hid_end_parse(d);
+       h->report_size = 0;
+       return (0);
+}
diff --git a/lib/libu4bhid/usage.c b/lib/libu4bhid/usage.c
new file mode 100644 (file)
index 0000000..3960dad
--- /dev/null
@@ -0,0 +1,239 @@
+/*     $NetBSD: usage.c,v 1.8 2000/10/10 19:23:58 is Exp $     */
+
+/*
+ * Copyright (c) 1999 Lennart Augustsson <augustss@netbsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "usbhid.h"
+
+#define _PATH_HIDTABLE "/usr/share/misc/usb_hid_usages"
+
+struct usage_in_page {
+       const char *name;
+       int usage;
+};
+
+static struct usage_page {
+       const char *name;
+       int usage;
+       struct usage_in_page *page_contents;
+       int pagesize, pagesizemax;
+} *pages;
+static int npages, npagesmax;
+
+#ifdef DEBUG
+void
+dump_hid_table(void)
+{
+       int i, j;
+
+       for (i = 0; i < npages; i++) {
+               printf("%d\t%s\n", pages[i].usage, pages[i].name);
+               for (j = 0; j < pages[i].pagesize; j++) {
+                       printf("\t%d\t%s\n", pages[i].page_contents[j].usage,
+                              pages[i].page_contents[j].name);
+               }
+       }
+}
+#endif
+
+void
+hid_init(const char *hidname)
+{
+       FILE *f;
+       char line[100], name[100], *p, *n;
+       int no;
+       int lineno;
+       struct usage_page *curpage = NULL;
+
+       if (hidname == NULL)
+               hidname = _PATH_HIDTABLE;
+
+       f = fopen(hidname, "r");
+       if (f == NULL)
+               err(1, "%s", hidname);
+       for (lineno = 1; ; lineno++) {
+               if (fgets(line, sizeof line, f) == NULL)
+                       break;
+               if (line[0] == '#')
+                       continue;
+               for (p = line; *p && isspace(*p); p++)
+                       ;
+               if (!*p)
+                       continue;
+               if (sscanf(line, " * %[^\n]", name) == 1)
+                       no = -1;
+               else if (sscanf(line, " 0x%x %[^\n]", &no, name) != 2 &&
+                        sscanf(line, " %d %[^\n]", &no, name) != 2)
+                       errx(1, "file %s, line %d, syntax error",
+                            hidname, lineno);
+               for (p = name; *p; p++)
+                       if (isspace(*p) || *p == '.')
+                               *p = '_';
+               n = strdup(name);
+               if (!n)
+                       err(1, "strdup");
+               if (isspace(line[0])) {
+                       if (!curpage)
+                               errx(1, "file %s, line %d, syntax error",
+                                    hidname, lineno);
+                       if (curpage->pagesize >= curpage->pagesizemax) {
+                               curpage->pagesizemax += 10;
+                               curpage->page_contents =
+                                       realloc(curpage->page_contents,
+                                               curpage->pagesizemax *
+                                               sizeof (struct usage_in_page));
+                               if (!curpage->page_contents)
+                                       err(1, "realloc");
+                       }
+                       curpage->page_contents[curpage->pagesize].name = n;
+                       curpage->page_contents[curpage->pagesize].usage = no;
+                       curpage->pagesize++;
+               } else {
+                       if (npages >= npagesmax) {
+                               if (pages == NULL) {
+                                       npagesmax = 5;
+                                       pages = malloc(npagesmax *
+                                                 sizeof (struct usage_page));
+                               } else {
+                                       npagesmax += 5;
+                                       pages = realloc(pages,
+                                                  npagesmax *
+                                                  sizeof (struct usage_page));
+                               }
+                               if (!pages)
+                                       err(1, "alloc");
+                       }
+                       curpage = &pages[npages++];
+                       curpage->name = n;
+                       curpage->usage = no;
+                       curpage->pagesize = 0;
+                       curpage->pagesizemax = 10;
+                       curpage->page_contents =
+                               malloc(curpage->pagesizemax *
+                                      sizeof (struct usage_in_page));
+                       if (!curpage->page_contents)
+                               err(1, "malloc");
+               }
+       }
+       fclose(f);
+#ifdef DEBUG
+       dump_hid_table();
+#endif
+}
+
+const char *
+hid_usage_page(int i)
+{
+       static char b[10];
+       int k;
+
+       if (!pages)
+               errx(1, "no hid table");
+
+       for (k = 0; k < npages; k++)
+               if (pages[k].usage == i)
+                       return pages[k].name;
+       sprintf(b, "0x%04x", i);
+       return b;
+}
+
+const char *
+hid_usage_in_page(unsigned int u)
+{
+       int page = HID_PAGE(u);
+       int i = HID_USAGE(u);
+       static char b[100];
+       int j, k, us;
+
+       for (k = 0; k < npages; k++)
+               if (pages[k].usage == page)
+                       break;
+       if (k >= npages)
+               goto bad;
+       for (j = 0; j < pages[k].pagesize; j++) {
+               us = pages[k].page_contents[j].usage;
+               if (us == -1) {
+                       sprintf(b,
+                           fmtcheck(pages[k].page_contents[j].name, "%d"),
+                           i);
+                       return b;
+               }
+               if (us == i)
+                       return pages[k].page_contents[j].name;
+       }
+ bad:
+       sprintf(b, "0x%04x", i);
+       return b;
+}
+
+int
+hid_parse_usage_page(const char *name)
+{
+       int k;
+
+       if (!pages)
+               errx(1, "no hid table");
+
+       for (k = 0; k < npages; k++)
+               if (strcmp(pages[k].name, name) == 0)
+                       return pages[k].usage;
+       return -1;
+}
+
+/* XXX handle hex */
+int
+hid_parse_usage_in_page(const char *name)
+{
+       const char *sep;
+       int k, j;
+       unsigned int l;
+
+       sep = strchr(name, ':');
+       if (sep == NULL)
+               return -1;
+       l = sep - name;
+       for (k = 0; k < npages; k++)
+               if (strncmp(pages[k].name, name, l) == 0)
+                       goto found;
+       return -1;
+ found:
+       sep++;
+       for (j = 0; j < pages[k].pagesize; j++)
+               if (strcmp(pages[k].page_contents[j].name, sep) == 0)
+                       return (pages[k].usage << 16) | pages[k].page_contents[j].usage;
+       return (-1);
+}
diff --git a/lib/libu4bhid/usbhid.3 b/lib/libu4bhid/usbhid.3
new file mode 100644 (file)
index 0000000..c34a109
--- /dev/null
@@ -0,0 +1,249 @@
+.\"    $NetBSD: usb.3,v 1.13 2000/09/24 02:17:52 augustss Exp $
+.\"
+.\" Copyright (c) 1999, 2001 Lennart Augustsson <augustss@NetBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 27, 2009
+.Dt USBHID 3
+.Os
+.Sh NAME
+.Nm usbhid ,
+.Nm hid_get_report_desc ,
+.Nm hid_get_report_id ,
+.Nm hid_use_report_desc ,
+.Nm hid_dispose_report_desc ,
+.Nm hid_start_parse ,
+.Nm hid_end_parse ,
+.Nm hid_get_item ,
+.Nm hid_report_size ,
+.Nm hid_locate ,
+.Nm hid_usage_page ,
+.Nm hid_usage_in_page ,
+.Nm hid_init ,
+.Nm hid_get_data ,
+.Nm hid_set_data ,
+.Nm hid_get_report ,
+.Nm hid_set_report
+.Nd USB HID access routines
+.Sh LIBRARY
+.Lb libusbhid
+.Sh SYNOPSIS
+.In usbhid.h
+.Ft report_desc_t
+.Fn hid_get_report_desc "int file"
+.Ft int
+.Fn hid_get_report_id "int file"
+.Ft int
+.Fn hid_set_immed "int fd" "int enable"
+.Ft report_desc_t
+.Fn hid_use_report_desc "unsigned char *data" "unsigned int size"
+.Ft void
+.Fn hid_dispose_report_desc "report_desc_t d"
+.Ft hid_data_t
+.Fn hid_start_parse "report_desc_t d" "int kindset" "int id"
+.Ft void
+.Fn hid_end_parse "hid_data_t s"
+.Ft int
+.Fn hid_get_item "hid_data_t s" "hid_item_t *h"
+.Ft int
+.Fn hid_report_size "report_desc_t d" "hid_kind_t k" "int id"
+.Ft int
+.Fn hid_locate "report_desc_t d" "u_int usage" "hid_kind_t k" "hid_item_t *h" "int id"
+.Ft "const char *"
+.Fn hid_usage_page "int i"
+.Ft "const char *"
+.Fn hid_usage_in_page "u_int u"
+.Ft int
+.Fn hid_parse_usage_page "const char *"
+.Ft int
+.Fn hid_parse_usage_in_page "const char *"
+.Ft void
+.Fn hid_init "const char *file"
+.Ft int
+.Fn hid_get_data "const void *data" "const hid_item_t *h"
+.Ft void
+.Fn hid_set_data "void *buf" "const hid_item_t *h" "int data"
+.Ft int
+.Fn hid_get_report "int fd" "enum hid_kind k" "unsigned char *data" "unsigned int size"
+.Ft int
+.Fn hid_set_report "int fd" "enum hid_kind k" "unsigned char *data" "unsigned int size"
+.Sh DESCRIPTION
+The
+.Nm
+library provides routines to extract data from USB Human Interface Devices.
+.Ss Introduction
+USB HID devices send and receive data layed out in a device dependent way.
+The
+.Nm
+library contains routines to extract the
+.Em "report descriptor"
+which contains the data layout information and then use this information.
+.Pp
+The routines can be divided into four parts: extraction of the descriptor,
+parsing of the descriptor, translating to/from symbolic names, and
+data manipulation.
+.Ss Synchronous HID operation
+Synchronous HID operation can be enabled or disabled by a call to
+.Fn hid_set_immed .
+If the second argument is zero synchronous HID operation is disabled.
+Else synchronous HID operation is enabled.
+The function returns a negative value on failure.
+.Pp
+.Fn hid_get_report
+and
+.Fn hid_set_report
+functions allow to synchronously get and set specific report if device
+supports it.
+For devices with multiple report IDs, wanted ID should be provided in the
+first byte of the buffer for both get and set.
+.Ss Descriptor Functions
+The report descriptor ID can be obtained by calling
+.Fn hid_get_report_id .
+A report descriptor can be obtained by calling
+.Fn hid_get_report_desc
+with a file descriptor obtained by opening a
+.Xr uhid 4
+device.
+Alternatively a data buffer containing the report descriptor can be
+passed into
+.Fn hid_use_report_desc .
+The data is copied into an internal structure.
+When the report descriptor
+is no longer needed it should be freed by calling
+.Fn hid_dispose_report_desc .
+The type
+.Vt report_desc_t
+is opaque and should be used when calling the parsing functions.
+If
+.Fn hid_dispose_report_desc
+fails it will return
+.Dv NULL .
+.Ss Descriptor Parsing Functions
+To parse the report descriptor the
+.Fn hid_start_parse
+function should be called with a report descriptor and a set that
+describes which items that are interesting.
+The set is obtained by OR-ing together values
+.Fa "(1 << k)"
+where
+.Fa k
+is an item of type
+.Vt hid_kind_t .
+The report ID (if present) is given by
+.Fa id .
+The function returns
+.Dv NULL
+if the initialization fails, otherwise an opaque value to be used
+in subsequent calls.
+After parsing the
+.Fn hid_end_parse
+function should be called to free internal data structures.
+.Pp
+To iterate through all the items in the report descriptor
+.Fn hid_get_item
+should be called while it returns a value greater than 0.
+When the report descriptor ends it will returns 0; a syntax
+error within the report descriptor will cause a return value less
+than 0.
+The struct pointed to by
+.Fa h
+will be filled with the relevant data for the item.
+The definition of
+.Vt hid_item_t
+can be found in
+.In usbhid.h
+and the meaning of the components in the USB HID documentation.
+.Pp
+Data should be read/written to the device in the size of
+the report.
+The size of a report (of a certain kind) can be computed by the
+.Fn hid_report_size
+function.
+If the report is prefixed by an ID byte it is given by
+.Fa id .
+.Pp
+To locate a single item the
+.Fn hid_locate
+function can be used.
+It should be given the usage code of
+the item and its kind and it will fill the item and return
+non-zero if the item was found.
+.Ss Name Translation Functions
+The function
+.Fn hid_usage_page
+will return the symbolic name of a usage page, and the function
+.Fn hid_usage_in_page
+will return the symbolic name of the usage within the page.
+Both these functions may return a pointer to static data.
+.Pp
+The functions
+.Fn hid_parse_usage_page
+and
+.Fn hid_parse_usage_in_page
+are the inverses of
+.Fn hid_usage_page
+and
+.Fn hid_usage_in_page .
+They take a usage string and return the number of the usage, or \-1
+if it cannot be found.
+.Pp
+Before any of these functions can be called the usage table
+must be parsed, this is done by calling
+.Fn hid_init
+with the name of the table.
+Passing
+.Dv NULL
+to this function will cause it to use the default table.
+.Ss Data Extraction Functions
+Given the data obtained from a HID device and an item in the
+report descriptor the
+.Fn hid_get_data
+function extracts the value of the item.
+Conversely
+.Fn hid_set_data
+can be used to put data into a report (which must be zeroed first).
+.Sh FILES
+.Bl -tag -width ".Pa /usr/share/misc/usb_hid_usages"
+.It Pa /usr/share/misc/usb_hid_usages
+The default HID usage table.
+.El
+.Sh EXAMPLES
+Not yet.
+.Sh SEE ALSO
+The
+.Tn USB
+specifications can be found at
+.Pa http://www.usb.org/developers/docs/ .
+.Pp
+.Xr uhid 4 ,
+.Xr usb 4
+.Sh HISTORY
+The
+.Nm
+library first appeared in
+.Nx 1.5 .
+.Sh BUGS
+This man page is woefully incomplete.
diff --git a/lib/libu4bhid/usbhid.h b/lib/libu4bhid/usbhid.h
new file mode 100644 (file)
index 0000000..231ee1f
--- /dev/null
@@ -0,0 +1,113 @@
+/*     $NetBSD: usb.h,v 1.8 2000/08/13 22:22:02 augustss Exp $ */
+
+/*
+ * Copyright (c) 1999 Lennart Augustsson <augustss@netbsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include <stdint.h>
+
+typedef struct report_desc *report_desc_t;
+
+typedef struct hid_data *hid_data_t;
+
+typedef enum hid_kind {
+       hid_input, hid_output, hid_feature, hid_collection, hid_endcollection
+} hid_kind_t;
+
+typedef struct hid_item {
+       /* Global */
+       uint32_t _usage_page;
+       int32_t logical_minimum;
+       int32_t logical_maximum;
+       int32_t physical_minimum;
+       int32_t physical_maximum;
+       int32_t unit_exponent;
+       int32_t unit;
+       int32_t report_size;
+       int32_t report_ID;
+#define NO_REPORT_ID 0
+       int32_t report_count;
+       /* Local */
+       uint32_t usage;
+       int32_t usage_minimum;
+       int32_t usage_maximum;
+       int32_t designator_index;
+       int32_t designator_minimum;
+       int32_t designator_maximum;
+       int32_t string_index;
+       int32_t string_minimum;
+       int32_t string_maximum;
+       int32_t set_delimiter;
+       /* Misc */
+       int32_t collection;
+       int     collevel;
+       enum hid_kind kind;
+       uint32_t flags;
+       /* Location */
+       uint32_t pos;
+       /* unused */
+       struct hid_item *next;
+} hid_item_t;
+
+#define HID_PAGE(u) (((u) >> 16) & 0xffff)
+#define HID_USAGE(u) ((u) & 0xffff)
+#define HID_HAS_GET_SET_REPORT 1
+
+__BEGIN_DECLS
+
+/* Obtaining a report descriptor, descr.c: */
+report_desc_t hid_get_report_desc(int file);
+report_desc_t hid_use_report_desc(unsigned char *data, unsigned int size);
+void hid_dispose_report_desc(report_desc_t);
+int hid_get_report_id(int file);
+int hid_set_immed(int fd, int enable);
+
+/* Parsing of a HID report descriptor, parse.c: */
+hid_data_t hid_start_parse(report_desc_t d, int kindset, int id);
+void hid_end_parse(hid_data_t s);
+int hid_get_item(hid_data_t s, hid_item_t *h);
+int hid_report_size(report_desc_t d, enum hid_kind k, int id);
+int hid_locate(report_desc_t d, unsigned int usage, enum hid_kind k,
+    hid_item_t *h, int id);
+
+/* Conversion to/from usage names, usage.c: */
+const char *hid_usage_page(int i);
+const char *hid_usage_in_page(unsigned int u);
+void hid_init(const char *file);
+int hid_parse_usage_in_page(const char *name);
+int hid_parse_usage_page(const char *name);
+
+/* Extracting/insertion of data, data.c: */
+int32_t hid_get_data(const void *p, const hid_item_t *h);
+void hid_set_data(void *p, const hid_item_t *h, int32_t data);
+int hid_get_report(int fd, enum hid_kind k,
+    unsigned char *data, unsigned int size);
+int hid_set_report(int fd, enum hid_kind k,
+    unsigned char *data, unsigned int size);
+
+__END_DECLS
diff --git a/lib/libu4bhid/usbvar.h b/lib/libu4bhid/usbvar.h
new file mode 100644 (file)
index 0000000..2722a37
--- /dev/null
@@ -0,0 +1,54 @@
+/*     $NetBSD: usbvar.h,v 1.2 1999/05/11 21:15:46 augustss Exp $      */
+
+/*
+ * Copyright (c) 1999 Lennart Augustsson <augustss@netbsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ *
+ */
+
+#ifndef _USBVAR_H_
+#define        _USBVAR_H_
+
+struct report_desc {
+       uint32_t size;
+       uint8_t data[1];
+};
+
+/* internal backwards compatibility functions */
+
+#ifdef HID_COMPAT7
+int    hid_set_immed_compat7(int fd, int enable);
+int    hid_get_report_id_compat7(int fd);
+report_desc_t  hid_get_report_desc_compat7(int fd);
+#endif
+
+#ifdef COMPAT_32BIT
+#define        hid_pass_ptr(ptr)       ((uint64_t)(uintptr_t)(ptr))
+#else
+#define        hid_pass_ptr(ptr)       (ptr)
+#endif
+
+#endif         /* _USBVAR_H_ */
diff --git a/sys/bus/u4b/Makefile b/sys/bus/u4b/Makefile
new file mode 100644 (file)
index 0000000..5dd6b64
--- /dev/null
@@ -0,0 +1,6 @@
+# $DragonFly: src/sys/bus/cam/Makefile,v 1.2 2007/11/12 07:27:50 pavalos Exp $
+#
+
+#SUBDIR=       controller input misc net quirk serial storage template
+
+.include <bsd.subdir.mk>
diff --git a/sys/bus/u4b/Makefile.usbdevs b/sys/bus/u4b/Makefile.usbdevs
new file mode 100644 (file)
index 0000000..f2a211e
--- /dev/null
@@ -0,0 +1,16 @@
+#       $DragonFly: src/sys/bus/pci/Makefile.pcidevs,v 1.1 2004/02/19 20:46:15 joerg Exp $
+#       $NetBSD: Makefile.pcidevs,v 1.2 1999/03/16 22:41:56 mjacob Exp $
+#
+# Update procedure:
+# 1.) Change "src/sys/bus/usb/usbdevs".
+# 2.) Commit "src/sys/bus/usb/usbdevs".
+# 3.) Execute "make -f Makefile.usbdevs" in "src/sys/bus/usb".
+# 4.) Commit "src/sys/bus/usb/usbdevs.h" and "src/sys/bus/usb/usbdevs_data.h".
+
+
+AWK=    awk
+
+usbdevs.h usbdevs_data.h: usbdevs usbdevs2h.awk
+       /bin/rm -f usbdevs.h usbdevs_data.h
+       ${AWK} -f usbdevs2h.awk usbdevs -d
+       ${AWK} -f usbdevs2h.awk usbdevs -h
diff --git a/sys/bus/u4b/controller/at91dci.c b/sys/bus/u4b/controller/at91dci.c
new file mode 100644 (file)
index 0000000..d325812
--- /dev/null
@@ -0,0 +1,2340 @@
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*-
+ * Copyright (c) 2007-2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the driver for the AT91 series USB Device
+ * Controller
+ */
+
+/*
+ * Thanks to "David Brownell" for helping out regarding the hardware
+ * endpoint profiles.
+ */
+
+/*
+ * NOTE: The "fifo_bank" is not reset in hardware when the endpoint is
+ * reset.
+ *
+ * NOTE: When the chip detects BUS-reset it will also reset the
+ * endpoints, Function-address and more.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define        USB_DEBUG_VAR at91dcidebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/at91dci.h>
+
+#define        AT9100_DCI_BUS2SC(bus) \
+   ((struct at91dci_softc *)(((uint8_t *)(bus)) - \
+    ((uint8_t *)&(((struct at91dci_softc *)0)->sc_bus))))
+
+#define        AT9100_DCI_PC2SC(pc) \
+   AT9100_DCI_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus)
+
+#ifdef USB_DEBUG
+static int at91dcidebug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, at91dci, CTLFLAG_RW, 0, "USB at91dci");
+SYSCTL_INT(_hw_usb_at91dci, OID_AUTO, debug, CTLFLAG_RW,
+    &at91dcidebug, 0, "at91dci debug level");
+#endif
+
+#define        AT9100_DCI_INTR_ENDPT 1
+
+/* prototypes */
+
+struct usb_bus_methods at91dci_bus_methods;
+struct usb_pipe_methods at91dci_device_bulk_methods;
+struct usb_pipe_methods at91dci_device_ctrl_methods;
+struct usb_pipe_methods at91dci_device_intr_methods;
+struct usb_pipe_methods at91dci_device_isoc_fs_methods;
+
+static at91dci_cmd_t at91dci_setup_rx;
+static at91dci_cmd_t at91dci_data_rx;
+static at91dci_cmd_t at91dci_data_tx;
+static at91dci_cmd_t at91dci_data_tx_sync;
+static void    at91dci_device_done(struct usb_xfer *, usb_error_t);
+static void    at91dci_do_poll(struct usb_bus *);
+static void    at91dci_standard_done(struct usb_xfer *);
+static void    at91dci_root_intr(struct at91dci_softc *sc);
+
+/*
+ * NOTE: Some of the bits in the CSR register have inverse meaning so
+ * we need a helper macro when acknowledging events:
+ */
+#define        AT91_CSR_ACK(csr, what) do {            \
+  (csr) &= ~((AT91_UDP_CSR_FORCESTALL|         \
+             AT91_UDP_CSR_TXPKTRDY|            \
+             AT91_UDP_CSR_RXBYTECNT) ^ (what));\
+  (csr) |= ((AT91_UDP_CSR_RX_DATA_BK0|         \
+            AT91_UDP_CSR_RX_DATA_BK1|          \
+            AT91_UDP_CSR_TXCOMP|               \
+            AT91_UDP_CSR_RXSETUP|              \
+            AT91_UDP_CSR_STALLSENT) ^ (what)); \
+} while (0)
+
+/*
+ * Here is a list of what the chip supports.
+ * Probably it supports more than listed here!
+ */
+static const struct usb_hw_ep_profile
+       at91dci_ep_profile[AT91_UDP_EP_MAX] = {
+
+       [0] = {
+               .max_in_frame_size = 8,
+               .max_out_frame_size = 8,
+               .is_simplex = 1,
+               .support_control = 1,
+       },
+       [1] = {
+               .max_in_frame_size = 64,
+               .max_out_frame_size = 64,
+               .is_simplex = 1,
+               .support_multi_buffer = 1,
+               .support_bulk = 1,
+               .support_interrupt = 1,
+               .support_isochronous = 1,
+               .support_in = 1,
+               .support_out = 1,
+       },
+       [2] = {
+               .max_in_frame_size = 64,
+               .max_out_frame_size = 64,
+               .is_simplex = 1,
+               .support_multi_buffer = 1,
+               .support_bulk = 1,
+               .support_interrupt = 1,
+               .support_isochronous = 1,
+               .support_in = 1,
+               .support_out = 1,
+       },
+       [3] = {
+               /* can also do BULK */
+               .max_in_frame_size = 8,
+               .max_out_frame_size = 8,
+               .is_simplex = 1,
+               .support_interrupt = 1,
+               .support_in = 1,
+               .support_out = 1,
+       },
+       [4] = {
+               .max_in_frame_size = 256,
+               .max_out_frame_size = 256,
+               .is_simplex = 1,
+               .support_multi_buffer = 1,
+               .support_bulk = 1,
+               .support_interrupt = 1,
+               .support_isochronous = 1,
+               .support_in = 1,
+               .support_out = 1,
+       },
+       [5] = {
+               .max_in_frame_size = 256,
+               .max_out_frame_size = 256,
+               .is_simplex = 1,
+               .support_multi_buffer = 1,
+               .support_bulk = 1,
+               .support_interrupt = 1,
+               .support_isochronous = 1,
+               .support_in = 1,
+               .support_out = 1,
+       },
+};
+
+static void
+at91dci_get_hw_ep_profile(struct usb_device *udev,
+    const struct usb_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+       if (ep_addr < AT91_UDP_EP_MAX) {
+               *ppf = (at91dci_ep_profile + ep_addr);
+       } else {
+               *ppf = NULL;
+       }
+}
+
+static void
+at91dci_clocks_on(struct at91dci_softc *sc)
+{
+       if (sc->sc_flags.clocks_off &&
+           sc->sc_flags.port_powered) {
+
+               DPRINTFN(5, "\n");
+
+               if (sc->sc_clocks_on) {
+                       (sc->sc_clocks_on) (sc->sc_clocks_arg);
+               }
+               sc->sc_flags.clocks_off = 0;
+
+               /* enable Transceiver */
+               AT91_UDP_WRITE_4(sc, AT91_UDP_TXVC, 0);
+       }
+}
+
+static void
+at91dci_clocks_off(struct at91dci_softc *sc)
+{
+       if (!sc->sc_flags.clocks_off) {
+
+               DPRINTFN(5, "\n");
+
+               /* disable Transceiver */
+               AT91_UDP_WRITE_4(sc, AT91_UDP_TXVC, AT91_UDP_TXVC_DIS);
+
+               if (sc->sc_clocks_off) {
+                       (sc->sc_clocks_off) (sc->sc_clocks_arg);
+               }
+               sc->sc_flags.clocks_off = 1;
+       }
+}
+
+static void
+at91dci_pull_up(struct at91dci_softc *sc)
+{
+       /* pullup D+, if possible */
+
+       if (!sc->sc_flags.d_pulled_up &&
+           sc->sc_flags.port_powered) {
+               sc->sc_flags.d_pulled_up = 1;
+               (sc->sc_pull_up) (sc->sc_pull_arg);
+       }
+}
+
+static void
+at91dci_pull_down(struct at91dci_softc *sc)
+{
+       /* pulldown D+, if possible */
+
+       if (sc->sc_flags.d_pulled_up) {
+               sc->sc_flags.d_pulled_up = 0;
+               (sc->sc_pull_down) (sc->sc_pull_arg);
+       }
+}
+
+static void
+at91dci_wakeup_peer(struct at91dci_softc *sc)
+{
+       if (!(sc->sc_flags.status_suspend)) {
+               return;
+       }
+
+       AT91_UDP_WRITE_4(sc, AT91_UDP_GSTATE, AT91_UDP_GSTATE_ESR);
+
+       /* wait 8 milliseconds */
+       /* Wait for reset to complete. */
+       usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+
+       AT91_UDP_WRITE_4(sc, AT91_UDP_GSTATE, 0);
+}
+
+static void
+at91dci_set_address(struct at91dci_softc *sc, uint8_t addr)
+{
+       DPRINTFN(5, "addr=%d\n", addr);
+
+       AT91_UDP_WRITE_4(sc, AT91_UDP_FADDR, addr |
+           AT91_UDP_FADDR_EN);
+}
+
+static uint8_t
+at91dci_setup_rx(struct at91dci_td *td)
+{
+       struct at91dci_softc *sc;
+       struct usb_device_request req;
+       uint32_t csr;
+       uint32_t temp;
+       uint16_t count;
+
+       /* read out FIFO status */
+       csr = bus_space_read_4(td->io_tag, td->io_hdl,
+           td->status_reg);
+
+       DPRINTFN(5, "csr=0x%08x rem=%u\n", csr, td->remainder);
+
+       temp = csr;
+       temp &= (AT91_UDP_CSR_RX_DATA_BK0 |
+           AT91_UDP_CSR_RX_DATA_BK1 |
+           AT91_UDP_CSR_STALLSENT |
+           AT91_UDP_CSR_RXSETUP |
+           AT91_UDP_CSR_TXCOMP);
+
+       if (!(csr & AT91_UDP_CSR_RXSETUP)) {
+               goto not_complete;
+       }
+       /* clear did stall */
+       td->did_stall = 0;
+
+       /* get the packet byte count */
+       count = (csr & AT91_UDP_CSR_RXBYTECNT) >> 16;
+
+       /* verify data length */
+       if (count != td->remainder) {
+               DPRINTFN(0, "Invalid SETUP packet "
+                   "length, %d bytes\n", count);
+               goto not_complete;
+       }
+       if (count != sizeof(req)) {
+               DPRINTFN(0, "Unsupported SETUP packet "
+                   "length, %d bytes\n", count);
+               goto not_complete;
+       }
+       /* receive data */
+       bus_space_read_multi_1(td->io_tag, td->io_hdl,
+           td->fifo_reg, (void *)&req, sizeof(req));
+
+       /* copy data into real buffer */
+       usbd_copy_in(td->pc, 0, &req, sizeof(req));
+
+       td->offset = sizeof(req);
+       td->remainder = 0;
+
+       /* get pointer to softc */
+       sc = AT9100_DCI_PC2SC(td->pc);
+
+       /* sneak peek the set address */
+       if ((req.bmRequestType == UT_WRITE_DEVICE) &&
+           (req.bRequest == UR_SET_ADDRESS)) {
+               sc->sc_dv_addr = req.wValue[0] & 0x7F;
+       } else {
+               sc->sc_dv_addr = 0xFF;
+       }
+
+       /* sneak peek the endpoint direction */
+       if (req.bmRequestType & UE_DIR_IN) {
+               csr |= AT91_UDP_CSR_DIR;
+       } else {
+               csr &= ~AT91_UDP_CSR_DIR;
+       }
+
+       /* write the direction of the control transfer */
+       AT91_CSR_ACK(csr, temp);
+       bus_space_write_4(td->io_tag, td->io_hdl,
+           td->status_reg, csr);
+       return (0);                     /* complete */
+
+not_complete:
+       /* abort any ongoing transfer */
+       if (!td->did_stall) {
+               DPRINTFN(5, "stalling\n");
+               temp |= AT91_UDP_CSR_FORCESTALL;
+               td->did_stall = 1;
+       }
+
+       /* clear interrupts, if any */
+       if (temp) {
+               DPRINTFN(5, "clearing 0x%08x\n", temp);
+               AT91_CSR_ACK(csr, temp);
+               bus_space_write_4(td->io_tag, td->io_hdl,
+                   td->status_reg, csr);
+       }
+       return (1);                     /* not complete */
+
+}
+
+static uint8_t
+at91dci_data_rx(struct at91dci_td *td)
+{
+       struct usb_page_search buf_res;
+       uint32_t csr;
+       uint32_t temp;
+       uint16_t count;
+       uint8_t to;
+       uint8_t got_short;
+
+       to = 2;                         /* don't loop forever! */
+       got_short = 0;
+
+       /* check if any of the FIFO banks have data */
+repeat:
+       /* read out FIFO status */
+       csr = bus_space_read_4(td->io_tag, td->io_hdl,
+           td->status_reg);
+
+       DPRINTFN(5, "csr=0x%08x rem=%u\n", csr, td->remainder);
+
+       if (csr & AT91_UDP_CSR_RXSETUP) {
+               if (td->remainder == 0) {
+                       /*
+                        * We are actually complete and have
+                        * received the next SETUP
+                        */
+                       DPRINTFN(5, "faking complete\n");
+                       return (0);     /* complete */
+               }
+               /*
+                * USB Host Aborted the transfer.
+                */
+               td->error = 1;
+               return (0);             /* complete */
+       }
+       /* Make sure that "STALLSENT" gets cleared */
+       temp = csr;
+       temp &= AT91_UDP_CSR_STALLSENT;
+
+       /* check status */
+       if (!(csr & (AT91_UDP_CSR_RX_DATA_BK0 |
+           AT91_UDP_CSR_RX_DATA_BK1))) {
+               if (temp) {
+                       /* write command */
+                       AT91_CSR_ACK(csr, temp);
+                       bus_space_write_4(td->io_tag, td->io_hdl,
+                           td->status_reg, csr);
+               }
+               return (1);             /* not complete */
+       }
+       /* get the packet byte count */
+       count = (csr & AT91_UDP_CSR_RXBYTECNT) >> 16;
+
+       /* verify the packet byte count */
+       if (count != td->max_packet_size) {
+               if (count < td->max_packet_size) {
+                       /* we have a short packet */
+                       td->short_pkt = 1;
+                       got_short = 1;
+               } else {
+                       /* invalid USB packet */
+                       td->error = 1;
+                       return (0);     /* we are complete */
+               }
+       }
+       /* verify the packet byte count */
+       if (count > td->remainder) {
+               /* invalid USB packet */
+               td->error = 1;
+               return (0);             /* we are complete */
+       }
+       while (count > 0) {
+               usbd_get_page(td->pc, td->offset, &buf_res);
+
+               /* get correct length */
+               if (buf_res.length > count) {
+                       buf_res.length = count;
+               }
+               /* receive data */
+               bus_space_read_multi_1(td->io_tag, td->io_hdl,
+                   td->fifo_reg, buf_res.buffer, buf_res.length);
+
+               /* update counters */
+               count -= buf_res.length;
+               td->offset += buf_res.length;
+               td->remainder -= buf_res.length;
+       }
+
+       /* clear status bits */
+       if (td->support_multi_buffer) {
+               if (td->fifo_bank) {
+                       td->fifo_bank = 0;
+                       temp |= AT91_UDP_CSR_RX_DATA_BK1;
+               } else {
+                       td->fifo_bank = 1;
+                       temp |= AT91_UDP_CSR_RX_DATA_BK0;
+               }
+       } else {
+               temp |= (AT91_UDP_CSR_RX_DATA_BK0 |
+                   AT91_UDP_CSR_RX_DATA_BK1);
+       }
+
+       /* write command */
+       AT91_CSR_ACK(csr, temp);
+       bus_space_write_4(td->io_tag, td->io_hdl,
+           td->status_reg, csr);
+
+       /*
+        * NOTE: We may have to delay a little bit before
+        * proceeding after clearing the DATA_BK bits.
+        */
+
+       /* check if we are complete */
+       if ((td->remainder == 0) || got_short) {
+               if (td->short_pkt) {
+                       /* we are complete */
+                       return (0);
+               }
+               /* else need to receive a zero length packet */
+       }
+       if (--to) {
+               goto repeat;
+       }
+       return (1);                     /* not complete */
+}
+
+static uint8_t
+at91dci_data_tx(struct at91dci_td *td)
+{
+       struct usb_page_search buf_res;
+       uint32_t csr;
+       uint32_t temp;
+       uint16_t count;
+       uint8_t to;
+
+       to = 2;                         /* don't loop forever! */
+
+repeat:
+
+       /* read out FIFO status */
+       csr = bus_space_read_4(td->io_tag, td->io_hdl,
+           td->status_reg);
+
+       DPRINTFN(5, "csr=0x%08x rem=%u\n", csr, td->remainder);
+
+       if (csr & AT91_UDP_CSR_RXSETUP) {
+               /*
+                * The current transfer was aborted
+                * by the USB Host
+                */
+               td->error = 1;
+               return (0);             /* complete */
+       }
+       /* Make sure that "STALLSENT" gets cleared */
+       temp = csr;
+       temp &= AT91_UDP_CSR_STALLSENT;
+
+       if (csr & AT91_UDP_CSR_TXPKTRDY) {
+               if (temp) {
+                       /* write command */
+                       AT91_CSR_ACK(csr, temp);
+                       bus_space_write_4(td->io_tag, td->io_hdl,
+                           td->status_reg, csr);
+               }
+               return (1);             /* not complete */
+       } else {
+               /* clear TXCOMP and set TXPKTRDY */
+               temp |= (AT91_UDP_CSR_TXCOMP |
+                   AT91_UDP_CSR_TXPKTRDY);
+       }
+
+       count = td->max_packet_size;
+       if (td->remainder < count) {
+               /* we have a short packet */
+               td->short_pkt = 1;
+               count = td->remainder;
+       }
+       while (count > 0) {
+
+               usbd_get_page(td->pc, td->offset, &buf_res);
+
+               /* get correct length */
+               if (buf_res.length > count) {
+                       buf_res.length = count;
+               }
+               /* transmit data */
+               bus_space_write_multi_1(td->io_tag, td->io_hdl,
+                   td->fifo_reg, buf_res.buffer, buf_res.length);
+
+               /* update counters */
+               count -= buf_res.length;
+               td->offset += buf_res.length;
+               td->remainder -= buf_res.length;
+       }
+
+       /* write command */
+       AT91_CSR_ACK(csr, temp);
+       bus_space_write_4(td->io_tag, td->io_hdl,
+           td->status_reg, csr);
+
+       /* check remainder */
+       if (td->remainder == 0) {
+               if (td->short_pkt) {
+                       return (0);     /* complete */
+               }
+               /* else we need to transmit a short packet */
+       }
+       if (--to) {
+               goto repeat;
+       }
+       return (1);                     /* not complete */
+}
+
+static uint8_t
+at91dci_data_tx_sync(struct at91dci_td *td)
+{
+       struct at91dci_softc *sc;
+       uint32_t csr;
+       uint32_t temp;
+
+#if 0
+repeat:
+#endif
+
+       /* read out FIFO status */
+       csr = bus_space_read_4(td->io_tag, td->io_hdl,
+           td->status_reg);
+
+       DPRINTFN(5, "csr=0x%08x\n", csr);
+
+       if (csr & AT91_UDP_CSR_RXSETUP) {
+               DPRINTFN(5, "faking complete\n");
+               /* Race condition */
+               return (0);             /* complete */
+       }
+       temp = csr;
+       temp &= (AT91_UDP_CSR_STALLSENT |
+           AT91_UDP_CSR_TXCOMP);
+
+       /* check status */
+       if (csr & AT91_UDP_CSR_TXPKTRDY) {
+               goto not_complete;
+       }
+       if (!(csr & AT91_UDP_CSR_TXCOMP)) {
+               goto not_complete;
+       }
+       sc = AT9100_DCI_PC2SC(td->pc);
+       if (sc->sc_dv_addr != 0xFF) {
+               /*
+                * The AT91 has a special requirement with regard to
+                * setting the address and that is to write the new
+                * address before clearing TXCOMP:
+                */
+               at91dci_set_address(sc, sc->sc_dv_addr);
+       }
+       /* write command */
+       AT91_CSR_ACK(csr, temp);
+       bus_space_write_4(td->io_tag, td->io_hdl,
+           td->status_reg, csr);
+
+       return (0);                     /* complete */
+
+not_complete:
+       if (temp) {
+               /* write command */
+               AT91_CSR_ACK(csr, temp);
+               bus_space_write_4(td->io_tag, td->io_hdl,
+                   td->status_reg, csr);
+       }
+       return (1);                     /* not complete */
+}
+
+static uint8_t
+at91dci_xfer_do_fifo(struct usb_xfer *xfer)
+{
+       struct at91dci_softc *sc;
+       struct at91dci_td *td;
+       uint8_t temp;
+
+       DPRINTFN(9, "\n");
+
+       td = xfer->td_transfer_cache;
+       while (1) {
+               if ((td->func) (td)) {
+                       /* operation in progress */
+                       break;
+               }
+               if (((void *)td) == xfer->td_transfer_last) {
+                       goto done;
+               }
+               if (td->error) {
+                       goto done;
+               } else if (td->remainder > 0) {
+                       /*
+                        * We had a short transfer. If there is no alternate
+                        * next, stop processing !
+                        */
+                       if (!td->alt_next) {
+                               goto done;
+                       }
+               }
+               /*
+                * Fetch the next transfer descriptor and transfer
+                * some flags to the next transfer descriptor
+                */
+               temp = 0;
+               if (td->fifo_bank)
+                       temp |= 1;
+               td = td->obj_next;
+               xfer->td_transfer_cache = td;
+               if (temp & 1)
+                       td->fifo_bank = 1;
+       }
+       return (1);                     /* not complete */
+
+done:
+       sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+       temp = (xfer->endpointno & UE_ADDR);
+
+       /* update FIFO bank flag and multi buffer */
+       if (td->fifo_bank) {
+               sc->sc_ep_flags[temp].fifo_bank = 1;
+       } else {
+               sc->sc_ep_flags[temp].fifo_bank = 0;
+       }
+
+       /* compute all actual lengths */
+
+       at91dci_standard_done(xfer);
+
+       return (0);                     /* complete */
+}
+
+static void
+at91dci_interrupt_poll(struct at91dci_softc *sc)
+{
+       struct usb_xfer *xfer;
+
+repeat:
+       TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+               if (!at91dci_xfer_do_fifo(xfer)) {
+                       /* queue has been modified */
+                       goto repeat;
+               }
+       }
+}
+
+void
+at91dci_vbus_interrupt(struct at91dci_softc *sc, uint8_t is_on)
+{
+       DPRINTFN(5, "vbus = %u\n", is_on);
+
+       USB_BUS_LOCK(&sc->sc_bus);
+       if (is_on) {
+               if (!sc->sc_flags.status_vbus) {
+                       sc->sc_flags.status_vbus = 1;
+
+                       /* complete root HUB interrupt endpoint */
+                       at91dci_root_intr(sc);
+               }
+       } else {
+               if (sc->sc_flags.status_vbus) {
+                       sc->sc_flags.status_vbus = 0;
+                       sc->sc_flags.status_bus_reset = 0;
+                       sc->sc_flags.status_suspend = 0;
+                       sc->sc_flags.change_suspend = 0;
+                       sc->sc_flags.change_connect = 1;
+
+                       /* complete root HUB interrupt endpoint */
+                       at91dci_root_intr(sc);
+               }
+       }
+       USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+at91dci_interrupt(struct at91dci_softc *sc)
+{
+       uint32_t status;
+
+       USB_BUS_LOCK(&sc->sc_bus);
+
+       status = AT91_UDP_READ_4(sc, AT91_UDP_ISR);
+       status &= AT91_UDP_INT_DEFAULT;
+
+       if (!status) {
+               USB_BUS_UNLOCK(&sc->sc_bus);
+               return;
+       }
+       /* acknowledge interrupts */
+
+       AT91_UDP_WRITE_4(sc, AT91_UDP_ICR, status);
+
+       /* check for any bus state change interrupts */
+
+       if (status & AT91_UDP_INT_BUS) {
+
+               DPRINTFN(5, "real bus interrupt 0x%08x\n", status);
+
+               if (status & AT91_UDP_INT_END_BR) {
+
+                       /* set correct state */
+                       sc->sc_flags.status_bus_reset = 1;
+                       sc->sc_flags.status_suspend = 0;
+                       sc->sc_flags.change_suspend = 0;
+                       sc->sc_flags.change_connect = 1;
+
+                       /* disable resume interrupt */
+                       AT91_UDP_WRITE_4(sc, AT91_UDP_IDR,
+                           AT91_UDP_INT_RXRSM);
+                       /* enable suspend interrupt */
+                       AT91_UDP_WRITE_4(sc, AT91_UDP_IER,
+                           AT91_UDP_INT_RXSUSP);
+               }
+               /*
+                * If RXRSM and RXSUSP is set at the same time we interpret
+                * that like RESUME. Resume is set when there is at least 3
+                * milliseconds of inactivity on the USB BUS.
+                */
+               if (status & AT91_UDP_INT_RXRSM) {
+                       if (sc->sc_flags.status_suspend) {
+                               sc->sc_flags.status_suspend = 0;
+                               sc->sc_flags.change_suspend = 1;
+
+                               /* disable resume interrupt */
+                               AT91_UDP_WRITE_4(sc, AT91_UDP_IDR,
+                                   AT91_UDP_INT_RXRSM);
+                               /* enable suspend interrupt */
+                               AT91_UDP_WRITE_4(sc, AT91_UDP_IER,
+                                   AT91_UDP_INT_RXSUSP);
+                       }
+               } else if (status & AT91_UDP_INT_RXSUSP) {
+                       if (!sc->sc_flags.status_suspend) {
+                               sc->sc_flags.status_suspend = 1;
+                               sc->sc_flags.change_suspend = 1;
+
+                               /* disable suspend interrupt */
+                               AT91_UDP_WRITE_4(sc, AT91_UDP_IDR,
+                                   AT91_UDP_INT_RXSUSP);
+
+                               /* enable resume interrupt */
+                               AT91_UDP_WRITE_4(sc, AT91_UDP_IER,
+                                   AT91_UDP_INT_RXRSM);
+                       }
+               }
+               /* complete root HUB interrupt endpoint */
+               at91dci_root_intr(sc);
+       }
+       /* check for any endpoint interrupts */
+
+       if (status & AT91_UDP_INT_EPS) {
+
+               DPRINTFN(5, "real endpoint interrupt 0x%08x\n", status);
+
+               at91dci_interrupt_poll(sc);
+       }
+       USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+at91dci_setup_standard_chain_sub(struct at91dci_std_temp *temp)
+{
+       struct at91dci_td *td;
+
+       /* get current Transfer Descriptor */
+       td = temp->td_next;
+       temp->td = td;
+
+       /* prepare for next TD */
+       temp->td_next = td->obj_next;
+
+       /* fill out the Transfer Descriptor */
+       td->func = temp->func;
+       td->pc = temp->pc;
+       td->offset = temp->offset;
+       td->remainder = temp->len;
+       td->fifo_bank = 0;
+       td->error = 0;
+       td->did_stall = temp->did_stall;
+       td->short_pkt = temp->short_pkt;
+       td->alt_next = temp->setup_alt_next;
+}
+
+static void
+at91dci_setup_standard_chain(struct usb_xfer *xfer)
+{
+       struct at91dci_std_temp temp;
+       struct at91dci_softc *sc;
+       struct at91dci_td *td;
+       uint32_t x;
+       uint8_t ep_no;
+       uint8_t need_sync;
+
+       DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+           xfer->address, UE_GET_ADDR(xfer->endpointno),
+           xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
+
+       temp.max_frame_size = xfer->max_frame_size;
+
+       td = xfer->td_start[0];
+       xfer->td_transfer_first = td;
+       xfer->td_transfer_cache = td;
+
+       /* setup temp */
+
+       temp.pc = NULL;
+       temp.td = NULL;
+       temp.td_next = xfer->td_start[0];
+       temp.offset = 0;
+       temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+       temp.did_stall = !xfer->flags_int.control_stall;
+
+       sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+       ep_no = (xfer->endpointno & UE_ADDR);
+
+       /* check if we should prepend a setup message */
+
+       if (xfer->flags_int.control_xfr) {
+               if (xfer->flags_int.control_hdr) {
+
+                       temp.func = &at91dci_setup_rx;
+                       temp.len = xfer->frlengths[0];
+                       temp.pc = xfer->frbuffers + 0;
+                       temp.short_pkt = temp.len ? 1 : 0;
+                       /* check for last frame */
+                       if (xfer->nframes == 1) {
+                               /* no STATUS stage yet, SETUP is last */
+                               if (xfer->flags_int.control_act)
+                                       temp.setup_alt_next = 0;
+                       }
+
+                       at91dci_setup_standard_chain_sub(&temp);
+               }
+               x = 1;
+       } else {
+               x = 0;
+       }
+
+       if (x != xfer->nframes) {
+               if (xfer->endpointno & UE_DIR_IN) {
+                       temp.func = &at91dci_data_tx;
+                       need_sync = 1;
+               } else {
+                       temp.func = &at91dci_data_rx;
+                       need_sync = 0;
+               }
+
+               /* setup "pc" pointer */
+               temp.pc = xfer->frbuffers + x;
+       } else {
+               need_sync = 0;
+       }
+       while (x != xfer->nframes) {
+
+               /* DATA0 / DATA1 message */
+
+               temp.len = xfer->frlengths[x];
+
+               x++;
+
+               if (x == xfer->nframes) {
+                       if (xfer->flags_int.control_xfr) {
+                               if (xfer->flags_int.control_act) {
+                                       temp.setup_alt_next = 0;
+                               }
+                       } else {
+                               temp.setup_alt_next = 0;
+                       }
+               }
+               if (temp.len == 0) {
+
+                       /* make sure that we send an USB packet */
+
+                       temp.short_pkt = 0;
+
+               } else {
+
+                       /* regular data transfer */
+
+                       temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+               }
+
+               at91dci_setup_standard_chain_sub(&temp);
+
+               if (xfer->flags_int.isochronous_xfr) {
+                       temp.offset += temp.len;
+               } else {
+                       /* get next Page Cache pointer */
+                       temp.pc = xfer->frbuffers + x;
+               }
+       }
+
+       /* check for control transfer */
+       if (xfer->flags_int.control_xfr) {
+
+               /* always setup a valid "pc" pointer for status and sync */
+               temp.pc = xfer->frbuffers + 0;
+               temp.len = 0;
+               temp.short_pkt = 0;
+               temp.setup_alt_next = 0;
+
+               /* check if we need to sync */
+               if (need_sync) {
+                       /* we need a SYNC point after TX */
+                       temp.func = &at91dci_data_tx_sync;
+                       at91dci_setup_standard_chain_sub(&temp);
+               }
+
+               /* check if we should append a status stage */
+               if (!xfer->flags_int.control_act) {
+
+                       /*
+                        * Send a DATA1 message and invert the current
+                        * endpoint direction.
+                        */
+                       if (xfer->endpointno & UE_DIR_IN) {
+                               temp.func = &at91dci_data_rx;
+                               need_sync = 0;
+                       } else {
+                               temp.func = &at91dci_data_tx;
+                               need_sync = 1;
+                       }
+
+                       at91dci_setup_standard_chain_sub(&temp);
+                       if (need_sync) {
+                               /* we need a SYNC point after TX */
+                               temp.func = &at91dci_data_tx_sync;
+                               at91dci_setup_standard_chain_sub(&temp);
+                       }
+               }
+       }
+
+       /* must have at least one frame! */
+       td = temp.td;
+       xfer->td_transfer_last = td;
+
+       /* setup the correct fifo bank */
+       if (sc->sc_ep_flags[ep_no].fifo_bank) {
+               td = xfer->td_transfer_first;
+               td->fifo_bank = 1;
+       }
+}
+
+static void
+at91dci_timeout(void *arg)
+{
+       struct usb_xfer *xfer = arg;
+
+       DPRINTF("xfer=%p\n", xfer);
+
+       USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+       /* transfer is transferred */
+       at91dci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+at91dci_start_standard_chain(struct usb_xfer *xfer)
+{
+       DPRINTFN(9, "\n");
+
+       /* poll one time */
+       if (at91dci_xfer_do_fifo(xfer)) {
+
+               struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+               uint8_t ep_no = xfer->endpointno & UE_ADDR;
+
+               /*
+                * Only enable the endpoint interrupt when we are actually
+                * waiting for data, hence we are dealing with level
+                * triggered interrupts !
+                */
+               AT91_UDP_WRITE_4(sc, AT91_UDP_IER, AT91_UDP_INT_EP(ep_no));
+
+               DPRINTFN(15, "enable interrupts on endpoint %d\n", ep_no);
+
+               /* put transfer on interrupt queue */
+               usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+               /* start timeout, if any */
+               if (xfer->timeout != 0) {
+                       usbd_transfer_timeout_ms(xfer,
+                           &at91dci_timeout, xfer->timeout);
+               }
+       }
+}
+
+static void
+at91dci_root_intr(struct at91dci_softc *sc)
+{
+       DPRINTFN(9, "\n");
+
+       USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+       /* set port bit */
+       sc->sc_hub_idata[0] = 0x02;     /* we only have one port */
+
+       uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+           sizeof(sc->sc_hub_idata));
+}
+
+static usb_error_t
+at91dci_standard_done_sub(struct usb_xfer *xfer)
+{
+       struct at91dci_td *td;
+       uint32_t len;
+       uint8_t error;
+
+       DPRINTFN(9, "\n");
+
+       td = xfer->td_transfer_cache;
+
+       do {
+               len = td->remainder;
+
+               if (xfer->aframes != xfer->nframes) {
+                       /*
+                        * Verify the length and subtract
+                        * the remainder from "frlengths[]":
+                        */
+                       if (len > xfer->frlengths[xfer->aframes]) {
+                               td->error = 1;
+                       } else {
+                               xfer->frlengths[xfer->aframes] -= len;
+                       }
+               }
+               /* Check for transfer error */
+               if (td->error) {
+                       /* the transfer is finished */
+                       error = 1;
+                       td = NULL;
+                       break;
+               }
+               /* Check for short transfer */
+               if (len > 0) {
+                       if (xfer->flags_int.short_frames_ok) {
+                               /* follow alt next */
+                               if (td->alt_next) {
+                                       td = td->obj_next;
+                               } else {
+                                       td = NULL;
+                               }
+                       } else {
+                               /* the transfer is finished */
+                               td = NULL;
+                       }
+                       error = 0;
+                       break;
+               }
+               td = td->obj_next;
+
+               /* this USB frame is complete */
+               error = 0;
+               break;
+
+       } while (0);
+
+       /* update transfer cache */
+
+       xfer->td_transfer_cache = td;
+
+       return (error ?
+           USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+at91dci_standard_done(struct usb_xfer *xfer)
+{
+       usb_error_t err = 0;
+
+       DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
+           xfer, xfer->endpoint);
+
+       /* reset scanner */
+
+       xfer->td_transfer_cache = xfer->td_transfer_first;
+
+       if (xfer->flags_int.control_xfr) {
+
+               if (xfer->flags_int.control_hdr) {
+
+                       err = at91dci_standard_done_sub(xfer);
+               }
+               xfer->aframes = 1;
+
+               if (xfer->td_transfer_cache == NULL) {
+                       goto done;
+               }
+       }
+       while (xfer->aframes != xfer->nframes) {
+
+               err = at91dci_standard_done_sub(xfer);
+               xfer->aframes++;
+
+               if (xfer->td_transfer_cache == NULL) {
+                       goto done;
+               }
+       }
+
+       if (xfer->flags_int.control_xfr &&
+           !xfer->flags_int.control_act) {
+
+               err = at91dci_standard_done_sub(xfer);
+       }
+done:
+       at91dci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ *     at91dci_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_done(struct usb_xfer *xfer, usb_error_t error)
+{
+       struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+       uint8_t ep_no;
+
+       USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+       DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n",
+           xfer, xfer->endpoint, error);
+
+       if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
+               ep_no = (xfer->endpointno & UE_ADDR);
+
+               /* disable endpoint interrupt */
+               AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, AT91_UDP_INT_EP(ep_no));
+
+               DPRINTFN(15, "disable interrupts on endpoint %d\n", ep_no);
+       }
+       /* dequeue transfer and start next transfer */
+       usbd_transfer_done(xfer, error);
+}
+
+static void
+at91dci_set_stall(struct usb_device *udev, struct usb_xfer *xfer,
+    struct usb_endpoint *ep, uint8_t *did_stall)
+{
+       struct at91dci_softc *sc;
+       uint32_t csr_val;
+       uint8_t csr_reg;
+
+       USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+       DPRINTFN(5, "endpoint=%p\n", ep);
+
+       if (xfer) {
+               /* cancel any ongoing transfers */
+               at91dci_device_done(xfer, USB_ERR_STALLED);
+       }
+       /* set FORCESTALL */
+       sc = AT9100_DCI_BUS2SC(udev->bus);
+       csr_reg = (ep->edesc->bEndpointAddress & UE_ADDR);
+       csr_reg = AT91_UDP_CSR(csr_reg);
+       csr_val = AT91_UDP_READ_4(sc, csr_reg);
+       AT91_CSR_ACK(csr_val, AT91_UDP_CSR_FORCESTALL);
+       AT91_UDP_WRITE_4(sc, csr_reg, csr_val);
+}
+
+static void
+at91dci_clear_stall_sub(struct at91dci_softc *sc, uint8_t ep_no,
+    uint8_t ep_type, uint8_t ep_dir)
+{
+       const struct usb_hw_ep_profile *pf;
+       uint32_t csr_val;
+       uint32_t temp;
+       uint8_t csr_reg;
+       uint8_t to;
+
+       if (ep_type == UE_CONTROL) {
+               /* clearing stall is not needed */
+               return;
+       }
+       /* compute CSR register offset */
+       csr_reg = AT91_UDP_CSR(ep_no);
+
+       /* compute default CSR value */
+       csr_val = 0;
+       AT91_CSR_ACK(csr_val, 0);
+
+       /* disable endpoint */
+       AT91_UDP_WRITE_4(sc, csr_reg, csr_val);
+
+       /* get endpoint profile */
+       at91dci_get_hw_ep_profile(NULL, &pf, ep_no);
+
+       /* reset FIFO */
+       AT91_UDP_WRITE_4(sc, AT91_UDP_RST, AT91_UDP_RST_EP(ep_no));
+       AT91_UDP_WRITE_4(sc, AT91_UDP_RST, 0);
+
+       /*
+        * NOTE: One would assume that a FIFO reset would release the
+        * FIFO banks aswell, but it doesn't! We have to do this
+        * manually!
+        */
+
+       /* release FIFO banks, if any */
+       for (to = 0; to != 2; to++) {
+
+               /* get csr value */
+               csr_val = AT91_UDP_READ_4(sc, csr_reg);
+
+               if (csr_val & (AT91_UDP_CSR_RX_DATA_BK0 |
+                   AT91_UDP_CSR_RX_DATA_BK1)) {
+                       /* clear status bits */
+                       if (pf->support_multi_buffer) {
+                               if (sc->sc_ep_flags[ep_no].fifo_bank) {
+                                       sc->sc_ep_flags[ep_no].fifo_bank = 0;
+                                       temp = AT91_UDP_CSR_RX_DATA_BK1;
+                               } else {
+                                       sc->sc_ep_flags[ep_no].fifo_bank = 1;
+                                       temp = AT91_UDP_CSR_RX_DATA_BK0;
+                               }
+                       } else {
+                               temp = (AT91_UDP_CSR_RX_DATA_BK0 |
+                                   AT91_UDP_CSR_RX_DATA_BK1);
+                       }
+               } else {
+                       temp = 0;
+               }
+
+               /* clear FORCESTALL */
+               temp |= AT91_UDP_CSR_STALLSENT;
+
+               AT91_CSR_ACK(csr_val, temp);
+               AT91_UDP_WRITE_4(sc, csr_reg, csr_val);
+       }
+
+       /* compute default CSR value */
+       csr_val = 0;
+       AT91_CSR_ACK(csr_val, 0);
+
+       /* enable endpoint */
+       csr_val &= ~AT91_UDP_CSR_ET_MASK;
+       csr_val |= AT91_UDP_CSR_EPEDS;
+
+       if (ep_type == UE_CONTROL) {
+               csr_val |= AT91_UDP_CSR_ET_CTRL;
+       } else {
+               if (ep_type == UE_BULK) {
+                       csr_val |= AT91_UDP_CSR_ET_BULK;
+               } else if (ep_type == UE_INTERRUPT) {
+                       csr_val |= AT91_UDP_CSR_ET_INT;
+               } else {
+                       csr_val |= AT91_UDP_CSR_ET_ISO;
+               }
+               if (ep_dir & UE_DIR_IN) {
+                       csr_val |= AT91_UDP_CSR_ET_DIR_IN;
+               }
+       }
+
+       /* enable endpoint */
+       AT91_UDP_WRITE_4(sc, AT91_UDP_CSR(ep_no), csr_val);
+}
+
+static void
+at91dci_clear_stall(struct usb_device *udev, struct usb_endpoint *ep)
+{
+       struct at91dci_softc *sc;
+       struct usb_endpoint_descriptor *ed;
+
+       DPRINTFN(5, "endpoint=%p\n", ep);
+
+       USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+       /* check mode */
+       if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+               /* not supported */
+               return;
+       }
+       /* get softc */
+       sc = AT9100_DCI_BUS2SC(udev->bus);
+
+       /* get endpoint descriptor */
+       ed = ep->edesc;
+
+       /* reset endpoint */
+       at91dci_clear_stall_sub(sc,
+           (ed->bEndpointAddress & UE_ADDR),
+           (ed->bmAttributes & UE_XFERTYPE),
+           (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb_error_t
+at91dci_init(struct at91dci_softc *sc)
+{
+       uint32_t csr_val;
+       uint8_t n;
+
+       DPRINTF("start\n");
+
+       /* set up the bus structure */
+       sc->sc_bus.usbrev = USB_REV_1_1;
+       sc->sc_bus.methods = &at91dci_bus_methods;
+
+       USB_BUS_LOCK(&sc->sc_bus);
+
+       /* turn on clocks */
+
+       if (sc->sc_clocks_on) {
+               (sc->sc_clocks_on) (sc->sc_clocks_arg);
+       }
+       /* wait a little for things to stabilise */
+       usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+
+       /* disable and clear all interrupts */
+
+       AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, 0xFFFFFFFF);
+       AT91_UDP_WRITE_4(sc, AT91_UDP_ICR, 0xFFFFFFFF);
+
+       /* compute default CSR value */
+
+       csr_val = 0;
+       AT91_CSR_ACK(csr_val, 0);
+
+       /* disable all endpoints */
+
+       for (n = 0; n != AT91_UDP_EP_MAX; n++) {
+
+               /* disable endpoint */
+               AT91_UDP_WRITE_4(sc, AT91_UDP_CSR(n), csr_val);
+       }
+
+       /* enable the control endpoint */
+
+       AT91_CSR_ACK(csr_val, AT91_UDP_CSR_ET_CTRL |
+           AT91_UDP_CSR_EPEDS);
+
+       /* write to FIFO control register */
+
+       AT91_UDP_WRITE_4(sc, AT91_UDP_CSR(0), csr_val);
+
+       /* enable the interrupts we want */
+
+       AT91_UDP_WRITE_4(sc, AT91_UDP_IER, AT91_UDP_INT_BUS);
+
+       /* turn off clocks */
+
+       at91dci_clocks_off(sc);
+
+       USB_BUS_UNLOCK(&sc->sc_bus);
+
+       /* catch any lost interrupts */
+
+       at91dci_do_poll(&sc->sc_bus);
+
+       return (0);                     /* success */
+}
+
+void
+at91dci_uninit(struct at91dci_softc *sc)
+{
+       USB_BUS_LOCK(&sc->sc_bus);
+
+       /* disable and clear all interrupts */
+       AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, 0xFFFFFFFF);
+       AT91_UDP_WRITE_4(sc, AT91_UDP_ICR, 0xFFFFFFFF);
+
+       sc->sc_flags.port_powered = 0;
+       sc->sc_flags.status_vbus = 0;
+       sc->sc_flags.status_bus_reset = 0;
+       sc->sc_flags.status_suspend = 0;
+       sc->sc_flags.change_suspend = 0;
+       sc->sc_flags.change_connect = 1;
+
+       at91dci_pull_down(sc);
+       at91dci_clocks_off(sc);
+       USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+at91dci_suspend(struct at91dci_softc *sc)
+{
+       /* TODO */
+}
+
+static void
+at91dci_resume(struct at91dci_softc *sc)
+{
+       /* TODO */
+}
+
+static void
+at91dci_do_poll(struct usb_bus *bus)
+{
+       struct at91dci_softc *sc = AT9100_DCI_BUS2SC(bus);
+
+       USB_BUS_LOCK(&sc->sc_bus);
+       at91dci_interrupt_poll(sc);
+       USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * at91dci bulk support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_bulk_open(struct usb_xfer *xfer)
+{
+       return;
+}
+
+static void
+at91dci_device_bulk_close(struct usb_xfer *xfer)
+{
+       at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_device_bulk_enter(struct usb_xfer *xfer)
+{
+       return;
+}
+
+static void
+at91dci_device_bulk_start(struct usb_xfer *xfer)
+{
+       /* setup TDs */
+       at91dci_setup_standard_chain(xfer);
+       at91dci_start_standard_chain(xfer);
+}
+
+struct usb_pipe_methods at91dci_device_bulk_methods =
+{
+       .open = at91dci_device_bulk_open,
+       .close = at91dci_device_bulk_close,
+       .enter = at91dci_device_bulk_enter,
+       .start = at91dci_device_bulk_start,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci control support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_ctrl_open(struct usb_xfer *xfer)
+{
+       return;
+}
+
+static void
+at91dci_device_ctrl_close(struct usb_xfer *xfer)
+{
+       at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_device_ctrl_enter(struct usb_xfer *xfer)
+{
+       return;
+}
+
+static void
+at91dci_device_ctrl_start(struct usb_xfer *xfer)
+{
+       /* setup TDs */
+       at91dci_setup_standard_chain(xfer);
+       at91dci_start_standard_chain(xfer);
+}
+
+struct usb_pipe_methods at91dci_device_ctrl_methods =
+{
+       .open = at91dci_device_ctrl_open,
+       .close = at91dci_device_ctrl_close,
+       .enter = at91dci_device_ctrl_enter,
+       .start = at91dci_device_ctrl_start,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_intr_open(struct usb_xfer *xfer)
+{
+       return;
+}
+
+static void
+at91dci_device_intr_close(struct usb_xfer *xfer)
+{
+       at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_device_intr_enter(struct usb_xfer *xfer)
+{
+       return;
+}
+
+static void
+at91dci_device_intr_start(struct usb_xfer *xfer)
+{
+       /* setup TDs */
+       at91dci_setup_standard_chain(xfer);
+       at91dci_start_standard_chain(xfer);
+}
+
+struct usb_pipe_methods at91dci_device_intr_methods =
+{
+       .open = at91dci_device_intr_open,
+       .close = at91dci_device_intr_close,
+       .enter = at91dci_device_intr_enter,
+       .start = at91dci_device_intr_start,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_isoc_fs_open(struct usb_xfer *xfer)
+{
+       return;
+}
+
+static void
+at91dci_device_isoc_fs_close(struct usb_xfer *xfer)
+{
+       at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_device_isoc_fs_enter(struct usb_xfer *xfer)
+{
+       struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+       uint32_t temp;
+       uint32_t nframes;
+
+       DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+           xfer, xfer->endpoint->isoc_next, xfer->nframes);
+
+       /* get the current frame index */
+
+       nframes = AT91_UDP_READ_4(sc, AT91_UDP_FRM);
+
+       /*
+        * check if the frame index is within the window where the frames
+        * will be inserted
+        */
+       temp = (nframes - xfer->endpoint->isoc_next) & AT91_UDP_FRM_MASK;
+
+       if ((xfer->endpoint->is_synced == 0) ||
+           (temp < xfer->nframes)) {
+               /*
+                * If there is data underflow or the endpoint queue is
+                * empty we schedule the transfer a few frames ahead
+                * of the current frame position. Else two isochronous
+                * transfers might overlap.
+                */
+               xfer->endpoint->isoc_next = (nframes + 3) & AT91_UDP_FRM_MASK;
+               xfer->endpoint->is_synced = 1;
+               DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
+       }
+       /*
+        * compute how many milliseconds the insertion is ahead of the
+        * current frame position:
+        */
+       temp = (xfer->endpoint->isoc_next - nframes) & AT91_UDP_FRM_MASK;
+
+       /*
+        * pre-compute when the isochronous transfer will be finished:
+        */
+       xfer->isoc_time_complete =
+           usb_isoc_time_expand(&sc->sc_bus, nframes) + temp +
+           xfer->nframes;
+
+       /* compute frame number for next insertion */
+       xfer->endpoint->isoc_next += xfer->nframes;
+
+       /* setup TDs */
+       at91dci_setup_standard_chain(xfer);
+}
+
+static void
+at91dci_device_isoc_fs_start(struct usb_xfer *xfer)
+{
+       /* start TD chain */
+       at91dci_start_standard_chain(xfer);
+}
+
+struct usb_pipe_methods at91dci_device_isoc_fs_methods =
+{
+       .open = at91dci_device_isoc_fs_open,
+       .close = at91dci_device_isoc_fs_close,
+       .enter = at91dci_device_isoc_fs_enter,
+       .start = at91dci_device_isoc_fs_start,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci root control support
+ *------------------------------------------------------------------------*
+ * Simulate a hardware HUB by handling all the necessary requests.
+ *------------------------------------------------------------------------*/
+
+static const struct usb_device_descriptor at91dci_devd = {
+       .bLength = sizeof(struct usb_device_descriptor),
+       .bDescriptorType = UDESC_DEVICE,
+       .bcdUSB = {0x00, 0x02},
+       .bDeviceClass = UDCLASS_HUB,
+       .bDeviceSubClass = UDSUBCLASS_HUB,
+       .bDeviceProtocol = UDPROTO_FSHUB,
+       .bMaxPacketSize = 64,
+       .bcdDevice = {0x00, 0x01},
+       .iManufacturer = 1,
+       .iProduct = 2,
+       .bNumConfigurations = 1,
+};
+
+static const struct at91dci_config_desc at91dci_confd = {
+       .confd = {
+               .bLength = sizeof(struct usb_config_descriptor),
+               .bDescriptorType = UDESC_CONFIG,
+               .wTotalLength[0] = sizeof(at91dci_confd),
+               .bNumInterface = 1,
+               .bConfigurationValue = 1,
+               .iConfiguration = 0,
+               .bmAttributes = UC_SELF_POWERED,
+               .bMaxPower = 0,
+       },
+       .ifcd = {
+               .bLength = sizeof(struct usb_interface_descriptor),
+               .bDescriptorType = UDESC_INTERFACE,
+               .bNumEndpoints = 1,
+               .bInterfaceClass = UICLASS_HUB,
+               .bInterfaceSubClass = UISUBCLASS_HUB,
+               .bInterfaceProtocol = 0,
+       },
+       .endpd = {
+               .bLength = sizeof(struct usb_endpoint_descriptor),
+               .bDescriptorType = UDESC_ENDPOINT,
+               .bEndpointAddress = (UE_DIR_IN | AT9100_DCI_INTR_ENDPT),
+               .bmAttributes = UE_INTERRUPT,
+               .wMaxPacketSize[0] = 8,
+               .bInterval = 255,
+       },
+};
+
+static const struct usb_hub_descriptor_min at91dci_hubd = {
+       .bDescLength = sizeof(at91dci_hubd),
+       .bDescriptorType = UDESC_HUB,
+       .bNbrPorts = 1,
+       .wHubCharacteristics[0] =
+       (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF,
+       .wHubCharacteristics[1] =
+       (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 8,
+       .bPwrOn2PwrGood = 50,
+       .bHubContrCurrent = 0,
+       .DeviceRemovable = {0},         /* port is removable */
+};
+
+#define        STRING_LANG \
+  0x09, 0x04,                          /* American English */
+
+#define        STRING_VENDOR \
+  'A', 0, 'T', 0, 'M', 0, 'E', 0, 'L', 0
+
+#define        STRING_PRODUCT \
+  'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \
+  'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \
+  'U', 0, 'B', 0,
+
+USB_MAKE_STRING_DESC(STRING_LANG, at91dci_langtab);
+USB_MAKE_STRING_DESC(STRING_VENDOR, at91dci_vendor);
+USB_MAKE_STRING_DESC(STRING_PRODUCT, at91dci_product);
+
+static usb_error_t
+at91dci_roothub_exec(struct usb_device *udev,
+    struct usb_device_request *req, const void **pptr, uint16_t *plength)
+{
+       struct at91dci_softc *sc = AT9100_DCI_BUS2SC(udev->bus);
+       const void *ptr;
+       uint16_t len;
+       uint16_t value;
+       uint16_t index;
+       usb_error_t err;
+
+       USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+       /* buffer reset */
+       ptr = (const void *)&sc->sc_hub_temp;
+       len = 0;
+       err = 0;
+
+       value = UGETW(req->wValue);
+       index = UGETW(req->wIndex);
+
+       /* demultiplex the control request */
+
+       switch (req->bmRequestType) {
+       case UT_READ_DEVICE:
+               switch (req->bRequest) {
+               case UR_GET_DESCRIPTOR:
+                       goto tr_handle_get_descriptor;
+               case UR_GET_CONFIG:
+                       goto tr_handle_get_config;
+               case UR_GET_STATUS:
+                       goto tr_handle_get_status;
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_WRITE_DEVICE:
+               switch (req->bRequest) {
+               case UR_SET_ADDRESS:
+                       goto tr_handle_set_address;
+               case UR_SET_CONFIG:
+                       goto tr_handle_set_config;
+               case UR_CLEAR_FEATURE:
+                       goto tr_valid;  /* nop */
+               case UR_SET_DESCRIPTOR:
+                       goto tr_valid;  /* nop */
+               case UR_SET_FEATURE:
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_WRITE_ENDPOINT:
+               switch (req->bRequest) {
+               case UR_CLEAR_FEATURE:
+                       switch (UGETW(req->wValue)) {
+                       case UF_ENDPOINT_HALT:
+                               goto tr_handle_clear_halt;
+                       case UF_DEVICE_REMOTE_WAKEUP:
+                               goto tr_handle_clear_wakeup;
+                       default:
+                               goto tr_stalled;
+                       }
+                       break;
+               case UR_SET_FEATURE:
+                       switch (UGETW(req->wValue)) {
+                       case UF_ENDPOINT_HALT:
+                               goto tr_handle_set_halt;
+                       case UF_DEVICE_REMOTE_WAKEUP:
+                               goto tr_handle_set_wakeup;
+                       default:
+                               goto tr_stalled;
+                       }
+                       break;
+               case UR_SYNCH_FRAME:
+                       goto tr_valid;  /* nop */
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_READ_ENDPOINT:
+               switch (req->bRequest) {
+               case UR_GET_STATUS:
+                       goto tr_handle_get_ep_status;
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_WRITE_INTERFACE:
+               switch (req->bRequest) {
+               case UR_SET_INTERFACE:
+                       goto tr_handle_set_interface;
+               case UR_CLEAR_FEATURE:
+                       goto tr_valid;  /* nop */
+               case UR_SET_FEATURE:
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_READ_INTERFACE:
+               switch (req->bRequest) {
+               case UR_GET_INTERFACE:
+                       goto tr_handle_get_interface;
+               case UR_GET_STATUS:
+                       goto tr_handle_get_iface_status;
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_WRITE_CLASS_INTERFACE:
+       case UT_WRITE_VENDOR_INTERFACE:
+               /* XXX forward */
+               break;
+
+       case UT_READ_CLASS_INTERFACE:
+       case UT_READ_VENDOR_INTERFACE:
+               /* XXX forward */
+               break;
+
+       case UT_WRITE_CLASS_DEVICE:
+               switch (req->bRequest) {
+               case UR_CLEAR_FEATURE:
+                       goto tr_valid;
+               case UR_SET_DESCRIPTOR:
+               case UR_SET_FEATURE:
+                       break;
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_WRITE_CLASS_OTHER:
+               switch (req->bRequest) {
+               case UR_CLEAR_FEATURE:
+                       goto tr_handle_clear_port_feature;
+               case UR_SET_FEATURE:
+                       goto tr_handle_set_port_feature;
+               case UR_CLEAR_TT_BUFFER:
+               case UR_RESET_TT:
+               case UR_STOP_TT:
+                       goto tr_valid;
+
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_READ_CLASS_OTHER:
+               switch (req->bRequest) {
+               case UR_GET_TT_STATE:
+                       goto tr_handle_get_tt_state;
+               case UR_GET_STATUS:
+                       goto tr_handle_get_port_status;
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_READ_CLASS_DEVICE:
+               switch (req->bRequest) {
+               case UR_GET_DESCRIPTOR:
+                       goto tr_handle_get_class_descriptor;
+               case UR_GET_STATUS:
+                       goto tr_handle_get_class_status;
+
+               default:
+                       goto tr_stalled;
+               }
+               break;
+       default:
+               goto tr_stalled;
+       }
+       goto tr_valid;
+
+tr_handle_get_descriptor:
+       switch (value >> 8) {
+       case UDESC_DEVICE:
+               if (value & 0xff) {
+                       goto tr_stalled;
+               }
+               len = sizeof(at91dci_devd);
+               ptr = (const void *)&at91dci_devd;
+               goto tr_valid;
+       case UDESC_CONFIG:
+               if (value & 0xff) {
+                       goto tr_stalled;
+               }
+               len = sizeof(at91dci_confd);
+               ptr = (const void *)&at91dci_confd;
+               goto tr_valid;
+       case UDESC_STRING:
+               switch (value & 0xff) {
+               case 0:         /* Language table */
+                       len = sizeof(at91dci_langtab);
+                       ptr = (const void *)&at91dci_langtab;
+                       goto tr_valid;
+
+               case 1:         /* Vendor */
+                       len = sizeof(at91dci_vendor);
+                       ptr = (const void *)&at91dci_vendor;
+                       goto tr_valid;
+
+               case 2:         /* Product */
+                       len = sizeof(at91dci_product);
+                       ptr = (const void *)&at91dci_product;
+                       goto tr_valid;
+               default:
+                       break;
+               }
+               break;
+       default:
+               goto tr_stalled;
+       }
+       goto tr_stalled;
+
+tr_handle_get_config:
+       len = 1;
+       sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+       goto tr_valid;
+
+tr_handle_get_status:
+       len = 2;
+       USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED);
+       goto tr_valid;
+
+tr_handle_set_address:
+       if (value & 0xFF00) {
+               goto tr_stalled;
+       }
+       sc->sc_rt_addr = value;
+       goto tr_valid;
+
+tr_handle_set_config:
+       if (value >= 2) {
+               goto tr_stalled;
+       }
+       sc->sc_conf = value;
+       goto tr_valid;
+
+tr_handle_get_interface:
+       len = 1;
+       sc->sc_hub_temp.wValue[0] = 0;
+       goto tr_valid;
+
+tr_handle_get_tt_state:
+tr_handle_get_class_status:
+tr_handle_get_iface_status:
+tr_handle_get_ep_status:
+       len = 2;
+       USETW(sc->sc_hub_temp.wValue, 0);
+       goto tr_valid;
+
+tr_handle_set_halt:
+tr_handle_set_interface:
+tr_handle_set_wakeup:
+tr_handle_clear_wakeup:
+tr_handle_clear_halt:
+       goto tr_valid;
+
+tr_handle_clear_port_feature:
+       if (index != 1) {
+               goto tr_stalled;
+       }
+       DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+
+       switch (value) {
+       case UHF_PORT_SUSPEND:
+               at91dci_wakeup_peer(sc);
+               break;
+
+       case UHF_PORT_ENABLE:
+               sc->sc_flags.port_enabled = 0;
+               break;
+
+       case UHF_PORT_TEST:
+       case UHF_PORT_INDICATOR:
+       case UHF_C_PORT_ENABLE:
+       case UHF_C_PORT_OVER_CURRENT:
+       case UHF_C_PORT_RESET:
+               /* nops */
+               break;
+       case UHF_PORT_POWER:
+               sc->sc_flags.port_powered = 0;
+               at91dci_pull_down(sc);
+               at91dci_clocks_off(sc);
+               break;
+       case UHF_C_PORT_CONNECTION:
+               sc->sc_flags.change_connect = 0;
+               break;
+       case UHF_C_PORT_SUSPEND:
+               sc->sc_flags.change_suspend = 0;
+               break;
+       default:
+               err = USB_ERR_IOERROR;
+               goto done;
+       }
+       goto tr_valid;
+
+tr_handle_set_port_feature:
+       if (index != 1) {
+               goto tr_stalled;
+       }
+       DPRINTFN(9, "UR_SET_PORT_FEATURE\n");
+
+       switch (value) {
+       case UHF_PORT_ENABLE:
+               sc->sc_flags.port_enabled = 1;
+               break;
+       case UHF_PORT_SUSPEND:
+       case UHF_PORT_RESET:
+       case UHF_PORT_TEST:
+       case UHF_PORT_INDICATOR:
+               /* nops */
+               break;
+       case UHF_PORT_POWER:
+               sc->sc_flags.port_powered = 1;
+               break;
+       default:
+               err = USB_ERR_IOERROR;
+               goto done;
+       }
+       goto tr_valid;
+
+tr_handle_get_port_status:
+
+       DPRINTFN(9, "UR_GET_PORT_STATUS\n");
+
+       if (index != 1) {
+               goto tr_stalled;
+       }
+       if (sc->sc_flags.status_vbus) {
+               at91dci_clocks_on(sc);
+               at91dci_pull_up(sc);
+       } else {
+               at91dci_pull_down(sc);
+               at91dci_clocks_off(sc);
+       }
+
+       /* Select FULL-speed and Device Side Mode */
+
+       value = UPS_PORT_MODE_DEVICE;
+
+       if (sc->sc_flags.port_powered) {
+               value |= UPS_PORT_POWER;
+       }
+       if (sc->sc_flags.port_enabled) {
+               value |= UPS_PORT_ENABLED;
+       }
+       if (sc->sc_flags.status_vbus &&
+           sc->sc_flags.status_bus_reset) {
+               value |= UPS_CURRENT_CONNECT_STATUS;
+       }
+       if (sc->sc_flags.status_suspend) {
+               value |= UPS_SUSPEND;
+       }
+       USETW(sc->sc_hub_temp.ps.wPortStatus, value);
+
+       value = 0;
+
+       if (sc->sc_flags.change_connect) {
+               value |= UPS_C_CONNECT_STATUS;
+
+               if (sc->sc_flags.status_vbus &&
+                   sc->sc_flags.status_bus_reset) {
+                       /* reset endpoint flags */
+                       memset(sc->sc_ep_flags, 0, sizeof(sc->sc_ep_flags));
+               }
+       }
+       if (sc->sc_flags.change_suspend) {
+               value |= UPS_C_SUSPEND;
+       }
+       USETW(sc->sc_hub_temp.ps.wPortChange, value);
+       len = sizeof(sc->sc_hub_temp.ps);
+       goto tr_valid;
+
+tr_handle_get_class_descriptor:
+       if (value & 0xFF) {
+               goto tr_stalled;
+       }
+       ptr = (const void *)&at91dci_hubd;
+       len = sizeof(at91dci_hubd);
+       goto tr_valid;
+
+tr_stalled:
+       err = USB_ERR_STALLED;
+tr_valid:
+done:
+       *plength = len;
+       *pptr = ptr;
+       return (err);
+}
+
+static void
+at91dci_xfer_setup(struct usb_setup_params *parm)
+{
+       const struct usb_hw_ep_profile *pf;
+       struct at91dci_softc *sc;
+       struct usb_xfer *xfer;
+       void *last_obj;
+       uint32_t ntd;
+       uint32_t n;
+       uint8_t ep_no;
+
+       sc = AT9100_DCI_BUS2SC(parm->udev->bus);
+       xfer = parm->curr_xfer;
+
+       /*
+        * NOTE: This driver does not use any of the parameters that
+        * are computed from the following values. Just set some
+        * reasonable dummies:
+        */
+       parm->hc_max_packet_size = 0x500;
+       parm->hc_max_packet_count = 1;
+       parm->hc_max_frame_size = 0x500;
+
+       usbd_transfer_setup_sub(parm);
+
+       /*
+        * compute maximum number of TDs
+        */
+       if (parm->methods == &at91dci_device_ctrl_methods) {
+
+               ntd = xfer->nframes + 1 /* STATUS */ + 1        /* SYNC 1 */
+                   + 1 /* SYNC 2 */ ;
+
+       } else if (parm->methods == &at91dci_device_bulk_methods) {
+
+               ntd = xfer->nframes + 1 /* SYNC */ ;
+
+       } else if (parm->methods == &at91dci_device_intr_methods) {
+
+               ntd = xfer->nframes + 1 /* SYNC */ ;
+
+       } else if (parm->methods == &at91dci_device_isoc_fs_methods) {
+
+               ntd = xfer->nframes + 1 /* SYNC */ ;
+
+       } else {
+
+               ntd = 0;
+       }
+
+       /*
+        * check if "usbd_transfer_setup_sub" set an error
+        */
+       if (parm->err) {
+               return;
+       }
+       /*
+        * allocate transfer descriptors
+        */
+       last_obj = NULL;
+
+       /*
+        * get profile stuff
+        */
+       if (ntd) {
+
+               ep_no = xfer->endpointno & UE_ADDR;
+               at91dci_get_hw_ep_profile(parm->udev, &pf, ep_no);
+
+               if (pf == NULL) {
+                       /* should not happen */
+                       parm->err = USB_ERR_INVAL;
+                       return;
+               }
+       } else {
+               ep_no = 0;
+               pf = NULL;
+       }
+
+       /* align data */
+       parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+       for (n = 0; n != ntd; n++) {
+
+               struct at91dci_td *td;
+
+               if (parm->buf) {
+
+                       td = USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+                       /* init TD */
+                       td->io_tag = sc->sc_io_tag;
+                       td->io_hdl = sc->sc_io_hdl;
+                       td->max_packet_size = xfer->max_packet_size;
+                       td->status_reg = AT91_UDP_CSR(ep_no);
+                       td->fifo_reg = AT91_UDP_FDR(ep_no);
+                       if (pf->support_multi_buffer) {
+                               td->support_multi_buffer = 1;
+                       }
+                       td->obj_next = last_obj;
+
+                       last_obj = td;
+               }
+               parm->size[0] += sizeof(*td);
+       }
+
+       xfer->td_start[0] = last_obj;
+}
+
+static void
+at91dci_xfer_unsetup(struct usb_xfer *xfer)
+{
+       return;
+}
+
+static void
+at91dci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
+    struct usb_endpoint *ep)
+{
+       struct at91dci_softc *sc = AT9100_DCI_BUS2SC(udev->bus);
+
+       DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+           ep, udev->address,
+           edesc->bEndpointAddress, udev->flags.usb_mode,
+           sc->sc_rt_addr);
+
+       if (udev->device_index != sc->sc_rt_addr) {
+
+               if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+                       /* not supported */
+                       return;
+               }
+               if (udev->speed != USB_SPEED_FULL) {
+                       /* not supported */
+                       return;
+               }
+               switch (edesc->bmAttributes & UE_XFERTYPE) {
+               case UE_CONTROL:
+                       ep->methods = &at91dci_device_ctrl_methods;
+                       break;
+               case UE_INTERRUPT:
+                       ep->methods = &at91dci_device_intr_methods;
+                       break;
+               case UE_ISOCHRONOUS:
+                       ep->methods = &at91dci_device_isoc_fs_methods;
+                       break;
+               case UE_BULK:
+                       ep->methods = &at91dci_device_bulk_methods;
+                       break;
+               default:
+                       /* do nothing */
+                       break;
+               }
+       }
+}
+
+static void
+at91dci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
+{
+       struct at91dci_softc *sc = AT9100_DCI_BUS2SC(bus);
+
+       switch (state) {
+       case USB_HW_POWER_SUSPEND:
+               at91dci_suspend(sc);
+               break;
+       case USB_HW_POWER_SHUTDOWN:
+               at91dci_uninit(sc);
+               break;
+       case USB_HW_POWER_RESUME:
+               at91dci_resume(sc);
+               break;
+       default:
+               break;
+       }
+}
+
+struct usb_bus_methods at91dci_bus_methods =
+{
+       .endpoint_init = &at91dci_ep_init,
+       .xfer_setup = &at91dci_xfer_setup,
+       .xfer_unsetup = &at91dci_xfer_unsetup,
+       .get_hw_ep_profile = &at91dci_get_hw_ep_profile,
+       .set_stall = &at91dci_set_stall,
+       .clear_stall = &at91dci_clear_stall,
+       .roothub_exec = &at91dci_roothub_exec,
+       .xfer_poll = &at91dci_do_poll,
+       .set_hw_power_sleep = &at91dci_set_hw_power_sleep,
+};
diff --git a/sys/bus/u4b/controller/at91dci.h b/sys/bus/u4b/controller/at91dci.h
new file mode 100644 (file)
index 0000000..b079eb5
--- /dev/null
@@ -0,0 +1,241 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2006 ATMEL
+ * Copyright (c) 2007 Hans Petter Selasky <hselasky@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB Device Port (UDP) register definition, based on "AT91RM9200.h" provided
+ * by ATMEL.
+ */
+
+#ifndef _AT9100_DCI_H_
+#define        _AT9100_DCI_H_
+
+#define        AT91_MAX_DEVICES (USB_MIN_DEVICES + 1)
+
+#define        AT91_UDP_FRM    0x00            /* Frame number register */
+#define        AT91_UDP_FRM_MASK     (0x7FF <<  0)     /* Frame Number as Defined in
+                                                * the Packet Field Formats */
+#define        AT91_UDP_FRM_ERR      (0x1 << 16)       /* Frame Error */
+#define        AT91_UDP_FRM_OK       (0x1 << 17)       /* Frame OK */
+
+#define        AT91_UDP_GSTATE 0x04            /* Global state register */
+#define        AT91_UDP_GSTATE_ADDR  (0x1 <<  0)       /* Addressed state */
+#define        AT91_UDP_GSTATE_CONFG (0x1 <<  1)       /* Configured */
+#define        AT91_UDP_GSTATE_ESR   (0x1 <<  2)       /* Enable Send Resume */
+#define        AT91_UDP_GSTATE_RSM   (0x1 <<  3)       /* A Resume Has Been Sent to
+                                                * the Host */
+#define        AT91_UDP_GSTATE_RMW   (0x1 <<  4)       /* Remote Wake Up Enable */
+
+#define        AT91_UDP_FADDR  0x08            /* Function Address Register */
+#define        AT91_UDP_FADDR_MASK  (0x7F << 0)/* Function Address Mask */
+#define        AT91_UDP_FADDR_EN    (0x1 <<  8)/* Function Enable */
+
+#define        AT91_UDP_RES0   0x0C            /* Reserved 0 */
+
+#define        AT91_UDP_IER    0x10            /* Interrupt Enable Register */
+#define        AT91_UDP_IDR    0x14            /* Interrupt Disable Register */
+#define        AT91_UDP_IMR    0x18            /* Interrupt Mask Register */
+#define        AT91_UDP_ISR    0x1C            /* Interrupt Status Register */
+#define        AT91_UDP_ICR    0x20            /* Interrupt Clear Register */
+#define        AT91_UDP_INT_EP(n)   (0x1 <<(n))/* Endpoint "n" Interrupt */
+#define        AT91_UDP_INT_RXSUSP  (0x1 <<  8)/* USB Suspend Interrupt */
+#define        AT91_UDP_INT_RXRSM   (0x1 <<  9)/* USB Resume Interrupt */
+#define        AT91_UDP_INT_EXTRSM  (0x1 << 10)/* USB External Resume Interrupt */
+#define        AT91_UDP_INT_SOFINT  (0x1 << 11)/* USB Start Of frame Interrupt */
+#define        AT91_UDP_INT_END_BR  (0x1 << 12)/* USB End Of Bus Reset Interrupt */
+#define        AT91_UDP_INT_WAKEUP  (0x1 << 13)/* USB Resume Interrupt */
+
+#define        AT91_UDP_INT_BUS \
+  (AT91_UDP_INT_RXSUSP|AT91_UDP_INT_RXRSM| \
+   AT91_UDP_INT_END_BR)
+
+#define        AT91_UDP_INT_EPS \
+  (AT91_UDP_INT_EP(0)|AT91_UDP_INT_EP(1)| \
+   AT91_UDP_INT_EP(2)|AT91_UDP_INT_EP(3)| \
+   AT91_UDP_INT_EP(4)|AT91_UDP_INT_EP(5))
+
+#define        AT91_UDP_INT_DEFAULT \
+  (AT91_UDP_INT_EPS|AT91_UDP_INT_BUS)
+
+#define        AT91_UDP_RES1   0x24            /* Reserved 1 */
+#define        AT91_UDP_RST    0x28            /* Reset Endpoint Register */
+#define        AT91_UDP_RST_EP(n) (0x1 <<  (n))/* Reset Endpoint "n" */
+
+#define        AT91_UDP_RES2   0x2C            /* Reserved 2 */
+
+#define        AT91_UDP_CSR(n) (0x30 + (4*(n)))/* Endpoint Control and Status
+                                        * Register */
+#define        AT91_UDP_CSR_TXCOMP (0x1 <<  0) /* Generates an IN packet with data
+                                        * previously written in the DPR */
+#define        AT91_UDP_CSR_RX_DATA_BK0 (0x1 <<  1)    /* Receive Data Bank 0 */
+#define        AT91_UDP_CSR_RXSETUP     (0x1 <<  2)    /* Sends STALL to the Host
+                                                * (Control endpoints) */
+#define        AT91_UDP_CSR_ISOERROR    (0x1 <<  3)    /* Isochronous error
+                                                * (Isochronous endpoints) */
+#define        AT91_UDP_CSR_STALLSENT   (0x1 <<  3)    /* Stall sent (Control, bulk,
+                                                * interrupt endpoints) */
+#define        AT91_UDP_CSR_TXPKTRDY    (0x1 <<  4)    /* Transmit Packet Ready */
+#define        AT91_UDP_CSR_FORCESTALL  (0x1 <<  5)    /* Force Stall (used by
+                                                * Control, Bulk and
+                                                * Isochronous endpoints). */
+#define        AT91_UDP_CSR_RX_DATA_BK1 (0x1 <<  6)    /* Receive Data Bank 1 (only
+                                                * used by endpoints with
+                                                * ping-pong attributes). */
+#define        AT91_UDP_CSR_DIR         (0x1 <<  7)    /* Transfer Direction */
+#define        AT91_UDP_CSR_ET_MASK     (0x7 <<  8)    /* Endpoint transfer type mask */
+#define        AT91_UDP_CSR_ET_CTRL     (0x0 <<  8)    /* Control IN+OUT */
+#define        AT91_UDP_CSR_ET_ISO      (0x1 <<  8)    /* Isochronous */
+#define        AT91_UDP_CSR_ET_BULK     (0x2 <<  8)    /* Bulk */
+#define        AT91_UDP_CSR_ET_INT      (0x3 <<  8)    /* Interrupt */
+#define        AT91_UDP_CSR_ET_DIR_OUT  (0x0 <<  8)    /* OUT tokens */
+#define        AT91_UDP_CSR_ET_DIR_IN   (0x4 <<  8)    /* IN tokens */
+#define        AT91_UDP_CSR_DTGLE       (0x1 << 11)    /* Data Toggle */
+#define        AT91_UDP_CSR_EPEDS       (0x1 << 15)    /* Endpoint Enable Disable */
+#define        AT91_UDP_CSR_RXBYTECNT   (0x7FF << 16)  /* Number Of Bytes Available
+                                                * in the FIFO */
+
+#define        AT91_UDP_FDR(n) (0x50 + (4*(n)))/* Endpoint FIFO Data Register */
+#define        AT91_UDP_RES3   0x70            /* Reserved 3 */
+#define        AT91_UDP_TXVC   0x74            /* Transceiver Control Register */
+#define        AT91_UDP_TXVC_DIS      (0x1 <<  8)
+
+#define        AT91_UDP_EP_MAX 6               /* maximum number of endpoints
+                                        * supported */
+
+#define        AT91_UDP_READ_4(sc, reg) \
+  bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define        AT91_UDP_WRITE_4(sc, reg, data) \
+  bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+struct at91dci_td;
+
+typedef uint8_t (at91dci_cmd_t)(struct at91dci_td *td);
+
+struct at91dci_td {
+       bus_space_tag_t io_tag;
+       bus_space_handle_t io_hdl;
+       struct at91dci_td *obj_next;
+       at91dci_cmd_t *func;
+       struct usb_page_cache *pc;
+       uint32_t offset;
+       uint32_t remainder;
+       uint16_t max_packet_size;
+       uint8_t status_reg;
+       uint8_t fifo_reg;
+       uint8_t fifo_bank:1;
+       uint8_t error:1;
+       uint8_t alt_next:1;
+       uint8_t short_pkt:1;
+       uint8_t support_multi_buffer:1;
+       uint8_t did_stall:1;
+};
+
+struct at91dci_std_temp {
+       at91dci_cmd_t *func;
+       struct usb_page_cache *pc;
+       struct at91dci_td *td;
+       struct at91dci_td *td_next;
+       uint32_t len;
+       uint32_t offset;
+       uint16_t max_frame_size;
+       uint8_t short_pkt;
+       /*
+         * short_pkt = 0: transfer should be short terminated
+         * short_pkt = 1: transfer should not be short terminated
+         */
+       uint8_t setup_alt_next;
+       uint8_t did_stall;
+};
+
+struct at91dci_config_desc {
+       struct usb_config_descriptor confd;
+       struct usb_interface_descriptor ifcd;
+       struct usb_endpoint_descriptor endpd;
+} __packed;
+
+union at91dci_hub_temp {
+       uWord   wValue;
+       struct usb_port_status ps;
+};
+
+struct at91dci_ep_flags {
+       uint8_t fifo_bank:1;            /* hardware specific */
+};
+
+struct at91dci_flags {
+       uint8_t change_connect:1;
+       uint8_t change_suspend:1;
+       uint8_t status_suspend:1;       /* set if suspended */
+       uint8_t status_vbus:1;          /* set if present */
+       uint8_t status_bus_reset:1;     /* set if reset complete */
+       uint8_t remote_wakeup:1;
+       uint8_t self_powered:1;
+       uint8_t clocks_off:1;
+       uint8_t port_powered:1;
+       uint8_t port_enabled:1;
+       uint8_t d_pulled_up:1;
+};
+
+struct at91dci_softc {
+       struct usb_bus sc_bus;
+       union at91dci_hub_temp sc_hub_temp;
+
+       struct usb_device *sc_devices[AT91_MAX_DEVICES];
+       struct resource *sc_io_res;
+       struct resource *sc_irq_res;
+       void   *sc_intr_hdl;
+       bus_size_t sc_io_size;
+       bus_space_tag_t sc_io_tag;
+       bus_space_handle_t sc_io_hdl;
+
+       void    (*sc_clocks_on) (void *arg);
+       void    (*sc_clocks_off) (void *arg);
+       void   *sc_clocks_arg;
+
+       void    (*sc_pull_up) (void *arg);
+       void    (*sc_pull_down) (void *arg);
+       void   *sc_pull_arg;
+
+       uint8_t sc_rt_addr;             /* root HUB address */
+       uint8_t sc_dv_addr;             /* device address */
+       uint8_t sc_conf;                /* root HUB config */
+
+       uint8_t sc_hub_idata[1];
+
+       struct at91dci_flags sc_flags;
+       struct at91dci_ep_flags sc_ep_flags[AT91_UDP_EP_MAX];
+};
+
+/* prototypes */
+
+usb_error_t at91dci_init(struct at91dci_softc *sc);
+void   at91dci_uninit(struct at91dci_softc *sc);
+void   at91dci_interrupt(struct at91dci_softc *sc);
+void   at91dci_vbus_interrupt(struct at91dci_softc *sc, uint8_t is_on);
+
+#endif                                 /* _AT9100_DCI_H_ */
diff --git a/sys/bus/u4b/controller/at91dci_atmelarm.c b/sys/bus/u4b/controller/at91dci_atmelarm.c
new file mode 100644 (file)
index 0000000..da9ba36
--- /dev/null
@@ -0,0 +1,346 @@
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*-
+ * Copyright (c) 2007-2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/at91dci.h>
+
+#include <sys/rman.h>
+
+#include <arm/at91/at91_pmcvar.h>
+#include <arm/at91/at91rm92reg.h>
+#include <arm/at91/at91_pio_rm9200.h>
+#include <arm/at91/at91_piovar.h>
+
+#define        MEM_RID 0
+
+/* Pin Definitions - do they belong here or somewhere else ? */
+
+#define        VBUS_MASK       AT91C_PIO_PB24
+#define        VBUS_BASE       AT91RM92_PIOB_BASE
+
+#define        PULLUP_MASK     AT91C_PIO_PB22
+#define        PULLUP_BASE     AT91RM92_PIOB_BASE
+
+static device_probe_t at91_udp_probe;
+static device_attach_t at91_udp_attach;
+static device_detach_t at91_udp_detach;
+
+struct at91_udp_softc {
+       struct at91dci_softc sc_dci;    /* must be first */
+       struct at91_pmc_clock *sc_iclk;
+       struct at91_pmc_clock *sc_fclk;
+       struct resource *sc_vbus_irq_res;
+       void   *sc_vbus_intr_hdl;
+};
+
+static void
+at91_vbus_poll(struct at91_udp_softc *sc)
+{
+       uint32_t temp;
+       uint8_t vbus_val;
+
+       /* XXX temporary clear interrupts here */
+
+       temp = at91_pio_gpio_clear_interrupt(VBUS_BASE);
+
+       /* just forward it */
+
+       vbus_val = at91_pio_gpio_get(VBUS_BASE, VBUS_MASK);
+       at91dci_vbus_interrupt(&sc->sc_dci, vbus_val);
+}
+
+static void
+at91_udp_clocks_on(void *arg)
+{
+       struct at91_udp_softc *sc = arg;
+
+       at91_pmc_clock_enable(sc->sc_iclk);
+       at91_pmc_clock_enable(sc->sc_fclk);
+}
+
+static void
+at91_udp_clocks_off(void *arg)
+{
+       struct at91_udp_softc *sc = arg;
+
+       at91_pmc_clock_disable(sc->sc_fclk);
+       at91_pmc_clock_disable(sc->sc_iclk);
+}
+
+static void
+at91_udp_pull_up(void *arg)
+{
+       at91_pio_gpio_set(PULLUP_BASE, PULLUP_MASK);
+}
+
+static void
+at91_udp_pull_down(void *arg)
+{
+       at91_pio_gpio_clear(PULLUP_BASE, PULLUP_MASK);
+}
+
+static int
+at91_udp_probe(device_t dev)
+{
+       device_set_desc(dev, "AT91 integrated AT91_UDP controller");
+       return (0);
+}
+
+static int
+at91_udp_attach(device_t dev)
+{
+       struct at91_udp_softc *sc = device_get_softc(dev);
+       int err;
+       int rid;
+
+       /* setup AT9100 USB device controller interface softc */
+
+       sc->sc_dci.sc_clocks_on = &at91_udp_clocks_on;
+       sc->sc_dci.sc_clocks_off = &at91_udp_clocks_off;
+       sc->sc_dci.sc_clocks_arg = sc;
+       sc->sc_dci.sc_pull_up = &at91_udp_pull_up;
+       sc->sc_dci.sc_pull_down = &at91_udp_pull_down;
+       sc->sc_dci.sc_pull_arg = sc;
+
+       /* initialise some bus fields */
+       sc->sc_dci.sc_bus.parent = dev;
+       sc->sc_dci.sc_bus.devices = sc->sc_dci.sc_devices;
+       sc->sc_dci.sc_bus.devices_max = AT91_MAX_DEVICES;
+
+       /* get all DMA memory */
+       if (usb_bus_mem_alloc_all(&sc->sc_dci.sc_bus,
+           USB_GET_DMA_TAG(dev), NULL)) {
+               return (ENOMEM);
+       }
+       /*
+        * configure VBUS input pin, enable deglitch and enable
+        * interrupt :
+        */
+       at91_pio_use_gpio(VBUS_BASE, VBUS_MASK);
+       at91_pio_gpio_input(VBUS_BASE, VBUS_MASK);
+       at91_pio_gpio_set_deglitch(VBUS_BASE, VBUS_MASK, 1);
+       at91_pio_gpio_set_interrupt(VBUS_BASE, VBUS_MASK, 1);
+
+       /*
+        * configure PULLUP output pin :
+        */
+       at91_pio_use_gpio(PULLUP_BASE, PULLUP_MASK);
+       at91_pio_gpio_output(PULLUP_BASE, PULLUP_MASK, 0);
+
+       at91_udp_pull_down(sc);
+
+       /* wait 10ms for pulldown to stabilise */
+       usb_pause_mtx(NULL, hz / 100);
+
+       sc->sc_iclk = at91_pmc_clock_ref("udc_clk");
+       sc->sc_fclk = at91_pmc_clock_ref("udpck");
+
+       rid = MEM_RID;
+       sc->sc_dci.sc_io_res =
+           bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+
+       if (!(sc->sc_dci.sc_io_res)) {
+               err = ENOMEM;
+               goto error;
+       }
+       sc->sc_dci.sc_io_tag = rman_get_bustag(sc->sc_dci.sc_io_res);
+       sc->sc_dci.sc_io_hdl = rman_get_bushandle(sc->sc_dci.sc_io_res);
+       sc->sc_dci.sc_io_size = rman_get_size(sc->sc_dci.sc_io_res);
+
+       rid = 0;
+       sc->sc_dci.sc_irq_res =
+           bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
+       if (!(sc->sc_dci.sc_irq_res)) {
+               goto error;
+       }
+       rid = 1;
+       sc->sc_vbus_irq_res =
+           bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
+       if (!(sc->sc_vbus_irq_res)) {
+               goto error;
+       }
+       sc->sc_dci.sc_bus.bdev = device_add_child(dev, "usbus", -1);
+       if (!(sc->sc_dci.sc_bus.bdev)) {
+               goto error;
+       }
+       device_set_ivars(sc->sc_dci.sc_bus.bdev, &sc->sc_dci.sc_bus);
+
+#if (__FreeBSD_version >= 700031)
+       err = bus_setup_intr(dev, sc->sc_dci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+           NULL, (driver_intr_t *)at91dci_interrupt, sc, &sc->sc_dci.sc_intr_hdl);
+#else
+       err = bus_setup_intr(dev, sc->sc_dci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+           (driver_intr_t *)at91dci_interrupt, sc, &sc->sc_dci.sc_intr_hdl);
+#endif
+       if (err) {
+               sc->sc_dci.sc_intr_hdl = NULL;
+               goto error;
+       }
+#if (__FreeBSD_version >= 700031)
+       err = bus_setup_intr(dev, sc->sc_vbus_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+           NULL, (driver_intr_t *)at91_vbus_poll, sc, &sc->sc_vbus_intr_hdl);
+#else
+       err = bus_setup_intr(dev, sc->sc_vbus_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+           (driver_intr_t *)at91_vbus_poll, sc, &sc->sc_vbus_intr_hdl);
+#endif
+       if (err) {
+               sc->sc_vbus_intr_hdl = NULL;
+               goto error;
+       }
+       err = at91dci_init(&sc->sc_dci);
+       if (!err) {
+               err = device_probe_and_attach(sc->sc_dci.sc_bus.bdev);
+       }
+       if (err) {
+               goto error;
+       } else {
+               /* poll VBUS one time */
+               at91_vbus_poll(sc);
+       }
+       return (0);
+
+error:
+       at91_udp_detach(dev);
+       return (ENXIO);
+}
+
+static int
+at91_udp_detach(device_t dev)
+{
+       struct at91_udp_softc *sc = device_get_softc(dev);
+       device_t bdev;
+       int err;
+
+       if (sc->sc_dci.sc_bus.bdev) {
+               bdev = sc->sc_dci.sc_bus.bdev;
+               device_detach(bdev);
+               device_delete_child(dev, bdev);
+       }
+       /* during module unload there are lots of children leftover */
+       device_delete_children(dev);
+
+       /* disable Transceiver */
+       AT91_UDP_WRITE_4(&sc->sc_dci, AT91_UDP_TXVC, AT91_UDP_TXVC_DIS);
+
+       /* disable and clear all interrupts */
+       AT91_UDP_WRITE_4(&sc->sc_dci, AT91_UDP_IDR, 0xFFFFFFFF);
+       AT91_UDP_WRITE_4(&sc->sc_dci, AT91_UDP_ICR, 0xFFFFFFFF);
+
+       /* disable VBUS interrupt */
+       at91_pio_gpio_set_interrupt(VBUS_BASE, VBUS_MASK, 0);
+
+       if (sc->sc_vbus_irq_res && sc->sc_vbus_intr_hdl) {
+               err = bus_teardown_intr(dev, sc->sc_vbus_irq_res,
+                   sc->sc_vbus_intr_hdl);
+               sc->sc_vbus_intr_hdl = NULL;
+       }
+       if (sc->sc_vbus_irq_res) {
+               bus_release_resource(dev, SYS_RES_IRQ, 1,
+                   sc->sc_vbus_irq_res);
+               sc->sc_vbus_irq_res = NULL;
+       }
+       if (sc->sc_dci.sc_irq_res && sc->sc_dci.sc_intr_hdl) {
+               /*
+                * only call at91_udp_uninit() after at91_udp_init()
+                */
+               at91dci_uninit(&sc->sc_dci);
+
+               err = bus_teardown_intr(dev, sc->sc_dci.sc_irq_res,
+                   sc->sc_dci.sc_intr_hdl);
+               sc->sc_dci.sc_intr_hdl = NULL;
+       }
+       if (sc->sc_dci.sc_irq_res) {
+               bus_release_resource(dev, SYS_RES_IRQ, 0,
+                   sc->sc_dci.sc_irq_res);
+               sc->sc_dci.sc_irq_res = NULL;
+       }
+       if (sc->sc_dci.sc_io_res) {
+               bus_release_resource(dev, SYS_RES_MEMORY, MEM_RID,
+                   sc->sc_dci.sc_io_res);
+               sc->sc_dci.sc_io_res = NULL;
+       }
+       usb_bus_mem_free_all(&sc->sc_dci.sc_bus, NULL);
+
+       /* disable clocks */
+       at91_pmc_clock_disable(sc->sc_iclk);
+       at91_pmc_clock_disable(sc->sc_fclk);
+       at91_pmc_clock_deref(sc->sc_fclk);
+       at91_pmc_clock_deref(sc->sc_iclk);
+
+       return (0);
+}
+
+static device_method_t at91_udp_methods[] = {
+       /* Device interface */
+       DEVMETHOD(device_probe, at91_udp_probe),
+       DEVMETHOD(device_attach, at91_udp_attach),
+       DEVMETHOD(device_detach, at91_udp_detach),
+       DEVMETHOD(device_suspend, bus_generic_suspend),
+       DEVMETHOD(device_resume, bus_generic_resume),
+       DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+       DEVMETHOD_END
+};
+
+static driver_t at91_udp_driver = {
+       .name = "at91_udp",
+       .methods = at91_udp_methods,
+       .size = sizeof(struct at91_udp_softc),
+};
+
+static devclass_t at91_udp_devclass;
+
+DRIVER_MODULE(at91_udp, atmelarm, at91_udp_driver, at91_udp_devclass, 0, 0);
diff --git a/sys/bus/u4b/controller/atmegadci.c b/sys/bus/u4b/controller/atmegadci.c
new file mode 100644 (file)
index 0000000..7db3bdb
--- /dev/null
@@ -0,0 +1,2160 @@
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*-
+ * Copyright (c) 2009 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the driver for the ATMEGA series USB OTG Controller. This
+ * driver currently only supports the DCI mode of the USB hardware.
+ */
+
+/*
+ * NOTE: When the chip detects BUS-reset it will also reset the
+ * endpoints, Function-address and more.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define        USB_DEBUG_VAR atmegadci_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/atmegadci.h>
+
+#define        ATMEGA_BUS2SC(bus) \
+   ((struct atmegadci_softc *)(((uint8_t *)(bus)) - \
+    ((uint8_t *)&(((struct atmegadci_softc *)0)->sc_bus))))
+
+#define        ATMEGA_PC2SC(pc) \
+   ATMEGA_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus)
+
+#ifdef USB_DEBUG
+static int atmegadci_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, atmegadci, CTLFLAG_RW, 0,
+    "USB ATMEGA DCI");
+SYSCTL_INT(_hw_usb_atmegadci, OID_AUTO, debug, CTLFLAG_RW,
+    &atmegadci_debug, 0, "ATMEGA DCI debug level");
+#endif
+
+#define        ATMEGA_INTR_ENDPT 1
+
+/* prototypes */
+
+struct usb_bus_methods atmegadci_bus_methods;
+struct usb_pipe_methods atmegadci_device_non_isoc_methods;
+struct usb_pipe_methods atmegadci_device_isoc_fs_methods;
+
+static atmegadci_cmd_t atmegadci_setup_rx;
+static atmegadci_cmd_t atmegadci_data_rx;
+static atmegadci_cmd_t atmegadci_data_tx;
+static atmegadci_cmd_t atmegadci_data_tx_sync;
+static void atmegadci_device_done(struct usb_xfer *, usb_error_t);
+static void atmegadci_do_poll(struct usb_bus *);
+static void atmegadci_standard_done(struct usb_xfer *);
+static void atmegadci_root_intr(struct atmegadci_softc *sc);
+
+/*
+ * Here is a list of what the chip supports:
+ */
+static const struct usb_hw_ep_profile
+       atmegadci_ep_profile[2] = {
+
+       [0] = {
+               .max_in_frame_size = 64,
+               .max_out_frame_size = 64,
+               .is_simplex = 1,
+               .support_control = 1,
+       },
+       [1] = {
+               .max_in_frame_size = 64,
+               .max_out_frame_size = 64,
+               .is_simplex = 1,
+               .support_bulk = 1,
+               .support_interrupt = 1,
+               .support_isochronous = 1,
+               .support_in = 1,
+               .support_out = 1,
+       },
+};
+
+static void
+atmegadci_get_hw_ep_profile(struct usb_device *udev,
+    const struct usb_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+       if (ep_addr == 0)
+               *ppf = atmegadci_ep_profile;
+       else if (ep_addr < ATMEGA_EP_MAX)
+               *ppf = atmegadci_ep_profile + 1;
+       else
+               *ppf = NULL;
+}
+
+static void
+atmegadci_clocks_on(struct atmegadci_softc *sc)
+{
+       if (sc->sc_flags.clocks_off &&
+           sc->sc_flags.port_powered) {
+
+               DPRINTFN(5, "\n");
+
+               /* turn on clocks */
+               (sc->sc_clocks_on) (&sc->sc_bus);
+
+               ATMEGA_WRITE_1(sc, ATMEGA_USBCON,
+                   ATMEGA_USBCON_USBE |
+                   ATMEGA_USBCON_OTGPADE |
+                   ATMEGA_USBCON_VBUSTE);
+
+               sc->sc_flags.clocks_off = 0;
+
+               /* enable transceiver ? */
+       }
+}
+
+static void
+atmegadci_clocks_off(struct atmegadci_softc *sc)
+{
+       if (!sc->sc_flags.clocks_off) {
+
+               DPRINTFN(5, "\n");
+
+               /* disable Transceiver ? */
+
+               ATMEGA_WRITE_1(sc, ATMEGA_USBCON,
+                   ATMEGA_USBCON_USBE |
+                   ATMEGA_USBCON_OTGPADE |
+                   ATMEGA_USBCON_FRZCLK |
+                   ATMEGA_USBCON_VBUSTE);
+
+               /* turn clocks off */
+               (sc->sc_clocks_off) (&sc->sc_bus);
+
+               sc->sc_flags.clocks_off = 1;
+       }
+}
+
+static void
+atmegadci_pull_up(struct atmegadci_softc *sc)
+{
+       /* pullup D+, if possible */
+
+       if (!sc->sc_flags.d_pulled_up &&
+           sc->sc_flags.port_powered) {
+               sc->sc_flags.d_pulled_up = 1;
+               ATMEGA_WRITE_1(sc, ATMEGA_UDCON, 0);
+       }
+}
+
+static void
+atmegadci_pull_down(struct atmegadci_softc *sc)
+{
+       /* pulldown D+, if possible */
+
+       if (sc->sc_flags.d_pulled_up) {
+               sc->sc_flags.d_pulled_up = 0;
+               ATMEGA_WRITE_1(sc, ATMEGA_UDCON, ATMEGA_UDCON_DETACH);
+       }
+}
+
+static void
+atmegadci_wakeup_peer(struct atmegadci_softc *sc)
+{
+       uint8_t temp;
+
+       if (!sc->sc_flags.status_suspend) {
+               return;
+       }
+
+       temp = ATMEGA_READ_1(sc, ATMEGA_UDCON);
+       ATMEGA_WRITE_1(sc, ATMEGA_UDCON, temp | ATMEGA_UDCON_RMWKUP);
+
+       /* wait 8 milliseconds */
+       /* Wait for reset to complete. */
+       usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+
+       /* hardware should have cleared RMWKUP bit */
+}
+
+static void
+atmegadci_set_address(struct atmegadci_softc *sc, uint8_t addr)
+{
+       DPRINTFN(5, "addr=%d\n", addr);
+
+       addr |= ATMEGA_UDADDR_ADDEN;
+
+       ATMEGA_WRITE_1(sc, ATMEGA_UDADDR, addr);
+}
+
+static uint8_t
+atmegadci_setup_rx(struct atmegadci_td *td)
+{
+       struct atmegadci_softc *sc;
+       struct usb_device_request req;
+       uint16_t count;
+       uint8_t temp;
+
+       /* get pointer to softc */
+       sc = ATMEGA_PC2SC(td->pc);
+
+       /* select endpoint number */
+       ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no);
+
+       /* check endpoint status */
+       temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX);
+
+       DPRINTFN(5, "UEINTX=0x%02x\n", temp);
+
+       if (!(temp & ATMEGA_UEINTX_RXSTPI)) {
+               goto not_complete;
+       }
+       /* clear did stall */
+       td->did_stall = 0;
+       /* get the packet byte count */
+       count =
+           (ATMEGA_READ_1(sc, ATMEGA_UEBCHX) << 8) |
+           (ATMEGA_READ_1(sc, ATMEGA_UEBCLX));
+
+       /* mask away undefined bits */
+       count &= 0x7FF;
+
+       /* verify data length */
+       if (count != td->remainder) {
+               DPRINTFN(0, "Invalid SETUP packet "
+                   "length, %d bytes\n", count);
+               goto not_complete;
+       }
+       if (count != sizeof(req)) {
+               DPRINTFN(0, "Unsupported SETUP packet "
+                   "length, %d bytes\n", count);
+               goto not_complete;
+       }
+       /* receive data */
+       ATMEGA_READ_MULTI_1(sc, ATMEGA_UEDATX,
+           (void *)&req, sizeof(req));
+
+       /* copy data into real buffer */
+       usbd_copy_in(td->pc, 0, &req, sizeof(req));
+
+       td->offset = sizeof(req);
+       td->remainder = 0;
+
+       /* sneak peek the set address */
+       if ((req.bmRequestType == UT_WRITE_DEVICE) &&
+           (req.bRequest == UR_SET_ADDRESS)) {
+               sc->sc_dv_addr = req.wValue[0] & 0x7F;
+               /* must write address before ZLP */
+               ATMEGA_WRITE_1(sc, ATMEGA_UDADDR, sc->sc_dv_addr);
+       } else {
+               sc->sc_dv_addr = 0xFF;
+       }
+
+       /* Clear SETUP packet interrupt and all other previous interrupts */
+       ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0);
+       return (0);                     /* complete */
+
+not_complete:
+       /* abort any ongoing transfer */
+       if (!td->did_stall) {
+               DPRINTFN(5, "stalling\n");
+               ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+                   ATMEGA_UECONX_EPEN |
+                   ATMEGA_UECONX_STALLRQ);
+               td->did_stall = 1;
+       }
+       if (temp & ATMEGA_UEINTX_RXSTPI) {
+               /* clear SETUP packet interrupt */
+               ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ~ATMEGA_UEINTX_RXSTPI);
+       }
+       /* we only want to know if there is a SETUP packet */
+       ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, ATMEGA_UEIENX_RXSTPE);
+       return (1);                     /* not complete */
+}
+
+static uint8_t
+atmegadci_data_rx(struct atmegadci_td *td)
+{
+       struct atmegadci_softc *sc;
+       struct usb_page_search buf_res;
+       uint16_t count;
+       uint8_t temp;
+       uint8_t to;
+       uint8_t got_short;
+
+       to = 3;                         /* don't loop forever! */
+       got_short = 0;
+
+       /* get pointer to softc */
+       sc = ATMEGA_PC2SC(td->pc);
+
+       /* select endpoint number */
+       ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no);
+
+repeat:
+       /* check if any of the FIFO banks have data */
+       /* check endpoint status */
+       temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX);
+
+       DPRINTFN(5, "temp=0x%02x rem=%u\n", temp, td->remainder);
+
+       if (temp & ATMEGA_UEINTX_RXSTPI) {
+               if (td->remainder == 0) {
+                       /*
+                        * We are actually complete and have
+                        * received the next SETUP
+                        */
+                       DPRINTFN(5, "faking complete\n");
+                       return (0);     /* complete */
+               }
+               /*
+                * USB Host Aborted the transfer.
+                */
+               td->error = 1;
+               return (0);             /* complete */
+       }
+       /* check status */
+       if (!(temp & (ATMEGA_UEINTX_FIFOCON |
+           ATMEGA_UEINTX_RXOUTI))) {
+               /* no data */
+               goto not_complete;
+       }
+       /* get the packet byte count */
+       count =
+           (ATMEGA_READ_1(sc, ATMEGA_UEBCHX) << 8) |
+           (ATMEGA_READ_1(sc, ATMEGA_UEBCLX));
+
+       /* mask away undefined bits */
+       count &= 0x7FF;
+
+       /* verify the packet byte count */
+       if (count != td->max_packet_size) {
+               if (count < td->max_packet_size) {
+                       /* we have a short packet */
+                       td->short_pkt = 1;
+                       got_short = 1;
+               } else {
+                       /* invalid USB packet */
+                       td->error = 1;
+                       return (0);     /* we are complete */
+               }
+       }
+       /* verify the packet byte count */
+       if (count > td->remainder) {
+               /* invalid USB packet */
+               td->error = 1;
+               return (0);             /* we are complete */
+       }
+       while (count > 0) {
+               usbd_get_page(td->pc, td->offset, &buf_res);
+
+               /* get correct length */
+               if (buf_res.length > count) {
+                       buf_res.length = count;
+               }
+               /* receive data */
+               ATMEGA_READ_MULTI_1(sc, ATMEGA_UEDATX,
+                   buf_res.buffer, buf_res.length);
+
+               /* update counters */
+               count -= buf_res.length;
+               td->offset += buf_res.length;
+               td->remainder -= buf_res.length;
+       }
+
+       /* clear OUT packet interrupt */
+       ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ATMEGA_UEINTX_RXOUTI ^ 0xFF);
+
+       /* release FIFO bank */
+       ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ATMEGA_UEINTX_FIFOCON ^ 0xFF);
+
+       /* check if we are complete */
+       if ((td->remainder == 0) || got_short) {
+               if (td->short_pkt) {
+                       /* we are complete */
+                       return (0);
+               }
+               /* else need to receive a zero length packet */
+       }
+       if (--to) {
+               goto repeat;
+       }
+not_complete:
+       /* we only want to know if there is a SETUP packet or OUT packet */
+       ATMEGA_WRITE_1(sc, ATMEGA_UEIENX,
+           ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_RXOUTE);
+       return (1);                     /* not complete */
+}
+
+static uint8_t
+atmegadci_data_tx(struct atmegadci_td *td)
+{
+       struct atmegadci_softc *sc;
+       struct usb_page_search buf_res;
+       uint16_t count;
+       uint8_t to;
+       uint8_t temp;
+
+       to = 3;                         /* don't loop forever! */
+
+       /* get pointer to softc */
+       sc = ATMEGA_PC2SC(td->pc);
+
+       /* select endpoint number */
+       ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no);
+
+repeat:
+
+       /* check endpoint status */
+       temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX);
+
+       DPRINTFN(5, "temp=0x%02x rem=%u\n", temp, td->remainder);
+
+       if (temp & ATMEGA_UEINTX_RXSTPI) {
+               /*
+                * The current transfer was aborted
+                * by the USB Host
+                */
+               td->error = 1;
+               return (0);             /* complete */
+       }
+
+       temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X);
+       if (temp & 3) {
+               /* cannot write any data - a bank is busy */
+               goto not_complete;
+       }
+
+       count = td->max_packet_size;
+       if (td->remainder < count) {
+               /* we have a short packet */
+               td->short_pkt = 1;
+               count = td->remainder;
+       }
+       while (count > 0) {
+
+               usbd_get_page(td->pc, td->offset, &buf_res);
+
+               /* get correct length */
+               if (buf_res.length > count) {
+                       buf_res.length = count;
+               }
+               /* transmit data */
+               ATMEGA_WRITE_MULTI_1(sc, ATMEGA_UEDATX,
+                   buf_res.buffer, buf_res.length);
+
+               /* update counters */
+               count -= buf_res.length;
+               td->offset += buf_res.length;
+               td->remainder -= buf_res.length;
+       }
+
+       /* clear IN packet interrupt */
+       ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0xFF ^ ATMEGA_UEINTX_TXINI);
+
+       /* allocate FIFO bank */
+       ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0xFF ^ ATMEGA_UEINTX_FIFOCON);
+
+       /* check remainder */
+       if (td->remainder == 0) {
+               if (td->short_pkt) {
+                       return (0);     /* complete */
+               }
+               /* else we need to transmit a short packet */
+       }
+       if (--to) {
+               goto repeat;
+       }
+not_complete:
+       /* we only want to know if there is a SETUP packet or free IN packet */
+       ATMEGA_WRITE_1(sc, ATMEGA_UEIENX,
+           ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_TXINE);
+       return (1);                     /* not complete */
+}
+
+static uint8_t
+atmegadci_data_tx_sync(struct atmegadci_td *td)
+{
+       struct atmegadci_softc *sc;
+       uint8_t temp;
+
+       /* get pointer to softc */
+       sc = ATMEGA_PC2SC(td->pc);
+
+       /* select endpoint number */
+       ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no);
+
+       /* check endpoint status */
+       temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX);
+
+       DPRINTFN(5, "temp=0x%02x\n", temp);
+
+       if (temp & ATMEGA_UEINTX_RXSTPI) {
+               DPRINTFN(5, "faking complete\n");
+               /* Race condition */
+               return (0);             /* complete */
+       }
+       /*
+        * The control endpoint has only got one bank, so if that bank
+        * is free the packet has been transferred!
+        */
+       temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X);
+       if (temp & 3) {
+               /* cannot write any data - a bank is busy */
+               goto not_complete;
+       }
+       if (sc->sc_dv_addr != 0xFF) {
+               /* set new address */
+               atmegadci_set_address(sc, sc->sc_dv_addr);
+       }
+       return (0);                     /* complete */
+
+not_complete:
+       /* we only want to know if there is a SETUP packet or free IN packet */
+       ATMEGA_WRITE_1(sc, ATMEGA_UEIENX,
+           ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_TXINE);
+       return (1);                     /* not complete */
+}
+
+static uint8_t
+atmegadci_xfer_do_fifo(struct usb_xfer *xfer)
+{
+       struct atmegadci_td *td;
+
+       DPRINTFN(9, "\n");
+
+       td = xfer->td_transfer_cache;
+       while (1) {
+               if ((td->func) (td)) {
+                       /* operation in progress */
+                       break;
+               }
+               if (((void *)td) == xfer->td_transfer_last) {
+                       goto done;
+               }
+               if (td->error) {
+                       goto done;
+               } else if (td->remainder > 0) {
+                       /*
+                        * We had a short transfer. If there is no alternate
+                        * next, stop processing !
+                        */
+                       if (!td->alt_next) {
+                               goto done;
+                       }
+               }
+               /*
+                * Fetch the next transfer descriptor and transfer
+                * some flags to the next transfer descriptor
+                */
+               td = td->obj_next;
+               xfer->td_transfer_cache = td;
+       }
+       return (1);                     /* not complete */
+
+done:
+       /* compute all actual lengths */
+
+       atmegadci_standard_done(xfer);
+       return (0);                     /* complete */
+}
+
+static void
+atmegadci_interrupt_poll(struct atmegadci_softc *sc)
+{
+       struct usb_xfer *xfer;
+
+repeat:
+       TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+               if (!atmegadci_xfer_do_fifo(xfer)) {
+                       /* queue has been modified */
+                       goto repeat;
+               }
+       }
+}
+
+static void
+atmegadci_vbus_interrupt(struct atmegadci_softc *sc, uint8_t is_on)
+{
+       DPRINTFN(5, "vbus = %u\n", is_on);
+
+       if (is_on) {
+               if (!sc->sc_flags.status_vbus) {
+                       sc->sc_flags.status_vbus = 1;
+
+                       /* complete root HUB interrupt endpoint */
+
+                       atmegadci_root_intr(sc);
+               }
+       } else {
+               if (sc->sc_flags.status_vbus) {
+                       sc->sc_flags.status_vbus = 0;
+                       sc->sc_flags.status_bus_reset = 0;
+                       sc->sc_flags.status_suspend = 0;
+                       sc->sc_flags.change_suspend = 0;
+                       sc->sc_flags.change_connect = 1;
+
+                       /* complete root HUB interrupt endpoint */
+
+                       atmegadci_root_intr(sc);
+               }
+       }
+}
+
+void
+atmegadci_interrupt(struct atmegadci_softc *sc)
+{
+       uint8_t status;
+
+       USB_BUS_LOCK(&sc->sc_bus);
+
+       /* read interrupt status */
+       status = ATMEGA_READ_1(sc, ATMEGA_UDINT);
+
+       /* clear all set interrupts */
+       ATMEGA_WRITE_1(sc, ATMEGA_UDINT, (~status) & 0x7D);
+
+       DPRINTFN(14, "UDINT=0x%02x\n", status);
+
+       /* check for any bus state change interrupts */
+       if (status & ATMEGA_UDINT_EORSTI) {
+
+               DPRINTFN(5, "end of reset\n");
+
+               /* set correct state */
+               sc->sc_flags.status_bus_reset = 1;
+               sc->sc_flags.status_suspend = 0;
+               sc->sc_flags.change_suspend = 0;
+               sc->sc_flags.change_connect = 1;
+
+               /* disable resume interrupt */
+               ATMEGA_WRITE_1(sc, ATMEGA_UDIEN,
+                   ATMEGA_UDINT_SUSPE |
+                   ATMEGA_UDINT_EORSTE);
+
+               /* complete root HUB interrupt endpoint */
+               atmegadci_root_intr(sc);
+       }
+       /*
+        * If resume and suspend is set at the same time we interpret
+        * that like RESUME. Resume is set when there is at least 3
+        * milliseconds of inactivity on the USB BUS.
+        */
+       if (status & ATMEGA_UDINT_WAKEUPI) {
+
+               DPRINTFN(5, "resume interrupt\n");
+
+               if (sc->sc_flags.status_suspend) {
+                       /* update status bits */
+                       sc->sc_flags.status_suspend = 0;
+                       sc->sc_flags.change_suspend = 1;
+
+                       /* disable resume interrupt */
+                       ATMEGA_WRITE_1(sc, ATMEGA_UDIEN,
+                           ATMEGA_UDINT_SUSPE |
+                           ATMEGA_UDINT_EORSTE);
+
+                       /* complete root HUB interrupt endpoint */
+                       atmegadci_root_intr(sc);
+               }
+       } else if (status & ATMEGA_UDINT_SUSPI) {
+
+               DPRINTFN(5, "suspend interrupt\n");
+
+               if (!sc->sc_flags.status_suspend) {
+                       /* update status bits */
+                       sc->sc_flags.status_suspend = 1;
+                       sc->sc_flags.change_suspend = 1;
+
+                       /* disable suspend interrupt */
+                       ATMEGA_WRITE_1(sc, ATMEGA_UDIEN,
+                           ATMEGA_UDINT_WAKEUPE |
+                           ATMEGA_UDINT_EORSTE);
+
+                       /* complete root HUB interrupt endpoint */
+                       atmegadci_root_intr(sc);
+               }
+       }
+       /* check VBUS */
+       status = ATMEGA_READ_1(sc, ATMEGA_USBINT);
+
+       /* clear all set interrupts */
+       ATMEGA_WRITE_1(sc, ATMEGA_USBINT, (~status) & 0x03);
+
+       if (status & ATMEGA_USBINT_VBUSTI) {
+               uint8_t temp;
+
+               DPRINTFN(5, "USBINT=0x%02x\n", status);
+
+               temp = ATMEGA_READ_1(sc, ATMEGA_USBSTA);
+               atmegadci_vbus_interrupt(sc, temp & ATMEGA_USBSTA_VBUS);
+       }
+       /* check for any endpoint interrupts */
+       status = ATMEGA_READ_1(sc, ATMEGA_UEINT);
+       /* the hardware will clear the UEINT bits automatically */
+       if (status) {
+
+               DPRINTFN(5, "real endpoint interrupt UEINT=0x%02x\n", status);
+
+               atmegadci_interrupt_poll(sc);
+       }
+       USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+atmegadci_setup_standard_chain_sub(struct atmegadci_std_temp *temp)
+{
+       struct atmegadci_td *td;
+
+       /* get current Transfer Descriptor */
+       td = temp->td_next;
+       temp->td = td;
+
+       /* prepare for next TD */
+       temp->td_next = td->obj_next;
+
+       /* fill out the Transfer Descriptor */
+       td->func = temp->func;
+       td->pc = temp->pc;
+       td->offset = temp->offset;
+       td->remainder = temp->len;
+       td->error = 0;
+       td->did_stall = temp->did_stall;
+       td->short_pkt = temp->short_pkt;
+       td->alt_next = temp->setup_alt_next;
+}
+
+static void
+atmegadci_setup_standard_chain(struct usb_xfer *xfer)
+{
+       struct atmegadci_std_temp temp;
+       struct atmegadci_softc *sc;
+       struct atmegadci_td *td;
+       uint32_t x;
+       uint8_t ep_no;
+       uint8_t need_sync;
+
+       DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+           xfer->address, UE_GET_ADDR(xfer->endpointno),
+           xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
+
+       temp.max_frame_size = xfer->max_frame_size;
+
+       td = xfer->td_start[0];
+       xfer->td_transfer_first = td;
+       xfer->td_transfer_cache = td;
+
+       /* setup temp */
+
+       temp.pc = NULL;
+       temp.td = NULL;
+       temp.td_next = xfer->td_start[0];
+       temp.offset = 0;
+       temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+       temp.did_stall = !xfer->flags_int.control_stall;
+
+       sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+       ep_no = (xfer->endpointno & UE_ADDR);
+
+       /* check if we should prepend a setup message */
+
+       if (xfer->flags_int.control_xfr) {
+               if (xfer->flags_int.control_hdr) {
+
+                       temp.func = &atmegadci_setup_rx;
+                       temp.len = xfer->frlengths[0];
+                       temp.pc = xfer->frbuffers + 0;
+                       temp.short_pkt = temp.len ? 1 : 0;
+                       /* check for last frame */
+                       if (xfer->nframes == 1) {
+                               /* no STATUS stage yet, SETUP is last */
+                               if (xfer->flags_int.control_act)
+                                       temp.setup_alt_next = 0;
+                       }
+
+                       atmegadci_setup_standard_chain_sub(&temp);
+               }
+               x = 1;
+       } else {
+               x = 0;
+       }
+
+       if (x != xfer->nframes) {
+               if (xfer->endpointno & UE_DIR_IN) {
+                       temp.func = &atmegadci_data_tx;
+                       need_sync = 1;
+               } else {
+                       temp.func = &atmegadci_data_rx;
+                       need_sync = 0;
+               }
+
+               /* setup "pc" pointer */
+               temp.pc = xfer->frbuffers + x;
+       } else {
+               need_sync = 0;
+       }
+       while (x != xfer->nframes) {
+
+               /* DATA0 / DATA1 message */
+
+               temp.len = xfer->frlengths[x];
+
+               x++;
+
+               if (x == xfer->nframes) {
+                       if (xfer->flags_int.control_xfr) {
+                               if (xfer->flags_int.control_act) {
+                                       temp.setup_alt_next = 0;
+                               }
+                       } else {
+                               temp.setup_alt_next = 0;
+                       }
+               }
+               if (temp.len == 0) {
+
+                       /* make sure that we send an USB packet */
+
+                       temp.short_pkt = 0;
+
+               } else {
+
+                       /* regular data transfer */
+
+                       temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+               }
+
+               atmegadci_setup_standard_chain_sub(&temp);
+
+               if (xfer->flags_int.isochronous_xfr) {
+                       temp.offset += temp.len;
+               } else {
+                       /* get next Page Cache pointer */
+                       temp.pc = xfer->frbuffers + x;
+               }
+       }
+
+       if (xfer->flags_int.control_xfr) {
+
+               /* always setup a valid "pc" pointer for status and sync */
+               temp.pc = xfer->frbuffers + 0;
+               temp.len = 0;
+               temp.short_pkt = 0;
+               temp.setup_alt_next = 0;
+
+               /* check if we need to sync */
+               if (need_sync) {
+                       /* we need a SYNC point after TX */
+                       temp.func = &atmegadci_data_tx_sync;
+                       atmegadci_setup_standard_chain_sub(&temp);
+               }
+
+               /* check if we should append a status stage */
+               if (!xfer->flags_int.control_act) {
+
+                       /*
+                        * Send a DATA1 message and invert the current
+                        * endpoint direction.
+                        */
+                       if (xfer->endpointno & UE_DIR_IN) {
+                               temp.func = &atmegadci_data_rx;
+                               need_sync = 0;
+                       } else {
+                               temp.func = &atmegadci_data_tx;
+                               need_sync = 1;
+                       }
+
+                       atmegadci_setup_standard_chain_sub(&temp);
+                       if (need_sync) {
+                               /* we need a SYNC point after TX */
+                               temp.func = &atmegadci_data_tx_sync;
+                               atmegadci_setup_standard_chain_sub(&temp);
+                       }
+               }
+       }
+       /* must have at least one frame! */
+       td = temp.td;
+       xfer->td_transfer_last = td;
+}
+
+static void
+atmegadci_timeout(void *arg)
+{
+       struct usb_xfer *xfer = arg;
+
+       DPRINTF("xfer=%p\n", xfer);
+
+       USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+       /* transfer is transferred */
+       atmegadci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+atmegadci_start_standard_chain(struct usb_xfer *xfer)
+{
+       DPRINTFN(9, "\n");
+
+       /* poll one time - will turn on interrupts */
+       if (atmegadci_xfer_do_fifo(xfer)) {
+
+               /* put transfer on interrupt queue */
+               usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+               /* start timeout, if any */
+               if (xfer->timeout != 0) {
+                       usbd_transfer_timeout_ms(xfer,
+                           &atmegadci_timeout, xfer->timeout);
+               }
+       }
+}
+
+static void
+atmegadci_root_intr(struct atmegadci_softc *sc)
+{
+       DPRINTFN(9, "\n");
+
+       USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+       /* set port bit */
+       sc->sc_hub_idata[0] = 0x02;     /* we only have one port */
+
+       uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+           sizeof(sc->sc_hub_idata));
+ }
+
+static usb_error_t
+atmegadci_standard_done_sub(struct usb_xfer *xfer)
+{
+       struct atmegadci_td *td;
+       uint32_t len;
+       uint8_t error;
+
+       DPRINTFN(9, "\n");
+
+       td = xfer->td_transfer_cache;
+
+       do {
+               len = td->remainder;
+
+               if (xfer->aframes != xfer->nframes) {
+                       /*
+                        * Verify the length and subtract
+                        * the remainder from "frlengths[]":
+                        */
+                       if (len > xfer->frlengths[xfer->aframes]) {
+                               td->error = 1;
+                       } else {
+                               xfer->frlengths[xfer->aframes] -= len;
+                       }
+               }
+               /* Check for transfer error */
+               if (td->error) {
+                       /* the transfer is finished */
+                       error = 1;
+                       td = NULL;
+                       break;
+               }
+               /* Check for short transfer */
+               if (len > 0) {
+                       if (xfer->flags_int.short_frames_ok) {
+                               /* follow alt next */
+                               if (td->alt_next) {
+                                       td = td->obj_next;
+                               } else {
+                                       td = NULL;
+                               }
+                       } else {
+                               /* the transfer is finished */
+                               td = NULL;
+                       }
+                       error = 0;
+                       break;
+               }
+               td = td->obj_next;
+
+               /* this USB frame is complete */
+               error = 0;
+               break;
+
+       } while (0);
+
+       /* update transfer cache */
+
+       xfer->td_transfer_cache = td;
+
+       return (error ?
+           USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+atmegadci_standard_done(struct usb_xfer *xfer)
+{
+       usb_error_t err = 0;
+
+       DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
+           xfer, xfer->endpoint);
+
+       /* reset scanner */
+
+       xfer->td_transfer_cache = xfer->td_transfer_first;
+
+       if (xfer->flags_int.control_xfr) {
+
+               if (xfer->flags_int.control_hdr) {
+
+                       err = atmegadci_standard_done_sub(xfer);
+               }
+               xfer->aframes = 1;
+
+               if (xfer->td_transfer_cache == NULL) {
+                       goto done;
+               }
+       }
+       while (xfer->aframes != xfer->nframes) {
+
+               err = atmegadci_standard_done_sub(xfer);
+               xfer->aframes++;
+
+               if (xfer->td_transfer_cache == NULL) {
+                       goto done;
+               }
+       }
+
+       if (xfer->flags_int.control_xfr &&
+           !xfer->flags_int.control_act) {
+
+               err = atmegadci_standard_done_sub(xfer);
+       }
+done:
+       atmegadci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ *     atmegadci_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_done(struct usb_xfer *xfer, usb_error_t error)
+{
+       struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+       uint8_t ep_no;
+
+       USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+       DPRINTFN(9, "xfer=%p, endpoint=%p, error=%d\n",
+           xfer, xfer->endpoint, error);
+
+       if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
+               ep_no = (xfer->endpointno & UE_ADDR);
+
+               /* select endpoint number */
+               ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no);
+
+               /* disable endpoint interrupt */
+               ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, 0);
+
+               DPRINTFN(15, "disabled interrupts!\n");
+       }
+       /* dequeue transfer and start next transfer */
+       usbd_transfer_done(xfer, error);
+}
+
+static void
+atmegadci_set_stall(struct usb_device *udev, struct usb_xfer *xfer,
+    struct usb_endpoint *ep, uint8_t *did_stall)
+{
+       struct atmegadci_softc *sc;
+       uint8_t ep_no;
+
+       USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+       DPRINTFN(5, "endpoint=%p\n", ep);
+
+       if (xfer) {
+               /* cancel any ongoing transfers */
+               atmegadci_device_done(xfer, USB_ERR_STALLED);
+       }
+       sc = ATMEGA_BUS2SC(udev->bus);
+       /* get endpoint number */
+       ep_no = (ep->edesc->bEndpointAddress & UE_ADDR);
+       /* select endpoint number */
+       ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no);
+       /* set stall */
+       ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+           ATMEGA_UECONX_EPEN |
+           ATMEGA_UECONX_STALLRQ);
+}
+
+static void
+atmegadci_clear_stall_sub(struct atmegadci_softc *sc, uint8_t ep_no,
+    uint8_t ep_type, uint8_t ep_dir)
+{
+       uint8_t temp;
+
+       if (ep_type == UE_CONTROL) {
+               /* clearing stall is not needed */
+               return;
+       }
+       /* select endpoint number */
+       ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no);
+
+       /* set endpoint reset */
+       ATMEGA_WRITE_1(sc, ATMEGA_UERST, ATMEGA_UERST_MASK(ep_no));
+
+       /* clear endpoint reset */
+       ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0);
+
+       /* set stall */
+       ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+           ATMEGA_UECONX_EPEN |
+           ATMEGA_UECONX_STALLRQ);
+
+       /* reset data toggle */
+       ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+           ATMEGA_UECONX_EPEN |
+           ATMEGA_UECONX_RSTDT);
+
+       /* clear stall */
+       ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+           ATMEGA_UECONX_EPEN |
+           ATMEGA_UECONX_STALLRQC);
+
+       do {
+               if (ep_type == UE_BULK) {
+                       temp = ATMEGA_UECFG0X_EPTYPE2;
+               } else if (ep_type == UE_INTERRUPT) {
+                       temp = ATMEGA_UECFG0X_EPTYPE3;
+               } else {
+                       temp = ATMEGA_UECFG0X_EPTYPE1;
+               }
+               if (ep_dir & UE_DIR_IN) {
+                       temp |= ATMEGA_UECFG0X_EPDIR;
+               }
+               /* two banks, 64-bytes wMaxPacket */
+               ATMEGA_WRITE_1(sc, ATMEGA_UECFG0X, temp);
+               ATMEGA_WRITE_1(sc, ATMEGA_UECFG1X,
+                   ATMEGA_UECFG1X_ALLOC |
+                   ATMEGA_UECFG1X_EPBK0 |      /* one bank */
+                   ATMEGA_UECFG1X_EPSIZE(3));
+
+               temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X);
+               if (!(temp & ATMEGA_UESTA0X_CFGOK)) {
+                       device_printf(sc->sc_bus.bdev,
+                           "Chip rejected configuration\n");
+               }
+       } while (0);
+}
+
+static void
+atmegadci_clear_stall(struct usb_device *udev, struct usb_endpoint *ep)
+{
+       struct atmegadci_softc *sc;
+       struct usb_endpoint_descriptor *ed;
+
+       DPRINTFN(5, "endpoint=%p\n", ep);
+
+       USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+       /* check mode */
+       if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+               /* not supported */
+               return;
+       }
+       /* get softc */
+       sc = ATMEGA_BUS2SC(udev->bus);
+
+       /* get endpoint descriptor */
+       ed = ep->edesc;
+
+       /* reset endpoint */
+       atmegadci_clear_stall_sub(sc,
+           (ed->bEndpointAddress & UE_ADDR),
+           (ed->bmAttributes & UE_XFERTYPE),
+           (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb_error_t
+atmegadci_init(struct atmegadci_softc *sc)
+{
+       uint8_t n;
+
+       DPRINTF("start\n");
+
+       /* set up the bus structure */
+       sc->sc_bus.usbrev = USB_REV_1_1;
+       sc->sc_bus.methods = &atmegadci_bus_methods;
+
+       USB_BUS_LOCK(&sc->sc_bus);
+
+       /* make sure USB is enabled */
+       ATMEGA_WRITE_1(sc, ATMEGA_USBCON,
+           ATMEGA_USBCON_USBE |
+           ATMEGA_USBCON_FRZCLK);
+
+       /* enable USB PAD regulator */
+       ATMEGA_WRITE_1(sc, ATMEGA_UHWCON,
+           ATMEGA_UHWCON_UVREGE |
+           ATMEGA_UHWCON_UIMOD);
+
+       /* the following register sets up the USB PLL, assuming 16MHz X-tal */
+       ATMEGA_WRITE_1(sc, 0x49 /* PLLCSR */, 0x14 | 0x02);
+
+       /* wait for PLL to lock */
+       for (n = 0; n != 20; n++) {
+               if (ATMEGA_READ_1(sc, 0x49) & 0x01)
+                       break;
+               /* wait a little bit for PLL to start */
+               usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+       }
+
+       /* make sure USB is enabled */
+       ATMEGA_WRITE_1(sc, ATMEGA_USBCON,
+           ATMEGA_USBCON_USBE |
+           ATMEGA_USBCON_OTGPADE |
+           ATMEGA_USBCON_VBUSTE);
+
+       /* turn on clocks */
+       (sc->sc_clocks_on) (&sc->sc_bus);
+
+       /* make sure device is re-enumerated */
+       ATMEGA_WRITE_1(sc, ATMEGA_UDCON, ATMEGA_UDCON_DETACH);
+
+       /* wait a little for things to stabilise */
+       usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 20);
+
+       /* enable interrupts */
+       ATMEGA_WRITE_1(sc, ATMEGA_UDIEN,
+           ATMEGA_UDINT_SUSPE |
+           ATMEGA_UDINT_EORSTE);
+
+       /* reset all endpoints */
+       ATMEGA_WRITE_1(sc, ATMEGA_UERST,
+           (1 << ATMEGA_EP_MAX) - 1);
+
+       /* disable reset */
+       ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0);
+
+       /* disable all endpoints */
+       for (n = 0; n != ATMEGA_EP_MAX; n++) {
+
+               /* select endpoint */
+               ATMEGA_WRITE_1(sc, ATMEGA_UENUM, n);
+
+               /* disable endpoint interrupt */
+               ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, 0);
+
+               /* disable endpoint */
+               ATMEGA_WRITE_1(sc, ATMEGA_UECONX, 0);
+       }
+
+       /* turn off clocks */
+
+       atmegadci_clocks_off(sc);
+
+       /* read initial VBUS state */
+
+       n = ATMEGA_READ_1(sc, ATMEGA_USBSTA);
+       atmegadci_vbus_interrupt(sc, n & ATMEGA_USBSTA_VBUS);
+
+       USB_BUS_UNLOCK(&sc->sc_bus);
+
+       /* catch any lost interrupts */
+
+       atmegadci_do_poll(&sc->sc_bus);
+
+       return (0);                     /* success */
+}
+
+void
+atmegadci_uninit(struct atmegadci_softc *sc)
+{
+       USB_BUS_LOCK(&sc->sc_bus);
+
+       /* turn on clocks */
+       (sc->sc_clocks_on) (&sc->sc_bus);
+
+       /* disable interrupts */
+       ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, 0);
+
+       /* reset all endpoints */
+       ATMEGA_WRITE_1(sc, ATMEGA_UERST,
+           (1 << ATMEGA_EP_MAX) - 1);
+
+       /* disable reset */
+       ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0);
+
+       sc->sc_flags.port_powered = 0;
+       sc->sc_flags.status_vbus = 0;
+       sc->sc_flags.status_bus_reset = 0;
+       sc->sc_flags.status_suspend = 0;
+       sc->sc_flags.change_suspend = 0;
+       sc->sc_flags.change_connect = 1;
+
+       atmegadci_pull_down(sc);
+       atmegadci_clocks_off(sc);
+
+       /* disable USB PAD regulator */
+       ATMEGA_WRITE_1(sc, ATMEGA_UHWCON, 0);
+
+       USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+atmegadci_suspend(struct atmegadci_softc *sc)
+{
+       /* TODO */
+}
+
+static void
+atmegadci_resume(struct atmegadci_softc *sc)
+{
+       /* TODO */
+}
+
+static void
+atmegadci_do_poll(struct usb_bus *bus)
+{
+       struct atmegadci_softc *sc = ATMEGA_BUS2SC(bus);
+
+       USB_BUS_LOCK(&sc->sc_bus);
+       atmegadci_interrupt_poll(sc);
+       USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * at91dci bulk support
+ * at91dci control support
+ * at91dci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_non_isoc_open(struct usb_xfer *xfer)
+{
+       return;
+}
+
+static void
+atmegadci_device_non_isoc_close(struct usb_xfer *xfer)
+{
+       atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_device_non_isoc_enter(struct usb_xfer *xfer)
+{
+       return;
+}
+
+static void
+atmegadci_device_non_isoc_start(struct usb_xfer *xfer)
+{
+       /* setup TDs */
+       atmegadci_setup_standard_chain(xfer);
+       atmegadci_start_standard_chain(xfer);
+}
+
+struct usb_pipe_methods atmegadci_device_non_isoc_methods =
+{
+       .open = atmegadci_device_non_isoc_open,
+       .close = atmegadci_device_non_isoc_close,
+       .enter = atmegadci_device_non_isoc_enter,
+       .start = atmegadci_device_non_isoc_start,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_isoc_fs_open(struct usb_xfer *xfer)
+{
+       return;
+}
+
+static void
+atmegadci_device_isoc_fs_close(struct usb_xfer *xfer)
+{
+       atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_device_isoc_fs_enter(struct usb_xfer *xfer)
+{
+       struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+       uint32_t temp;
+       uint32_t nframes;
+
+       DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+           xfer, xfer->endpoint->isoc_next, xfer->nframes);
+
+       /* get the current frame index */
+
+       nframes =
+           (ATMEGA_READ_1(sc, ATMEGA_UDFNUMH) << 8) |
+           (ATMEGA_READ_1(sc, ATMEGA_UDFNUML));
+
+       nframes &= ATMEGA_FRAME_MASK;
+
+       /*
+        * check if the frame index is within the window where the frames
+        * will be inserted
+        */
+       temp = (nframes - xfer->endpoint->isoc_next) & ATMEGA_FRAME_MASK;
+
+       if ((xfer->endpoint->is_synced == 0) ||
+           (temp < xfer->nframes)) {
+               /*
+                * If there is data underflow or the pipe queue is
+                * empty we schedule the transfer a few frames ahead
+                * of the current frame position. Else two isochronous
+                * transfers might overlap.
+                */
+               xfer->endpoint->isoc_next = (nframes + 3) & ATMEGA_FRAME_MASK;
+               xfer->endpoint->is_synced = 1;
+               DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
+       }
+       /*
+        * compute how many milliseconds the insertion is ahead of the
+        * current frame position:
+        */
+       temp = (xfer->endpoint->isoc_next - nframes) & ATMEGA_FRAME_MASK;
+
+       /*
+        * pre-compute when the isochronous transfer will be finished:
+        */
+       xfer->isoc_time_complete =
+           usb_isoc_time_expand(&sc->sc_bus, nframes) + temp +
+           xfer->nframes;
+
+       /* compute frame number for next insertion */
+       xfer->endpoint->isoc_next += xfer->nframes;
+
+       /* setup TDs */
+       atmegadci_setup_standard_chain(xfer);
+}
+
+static void
+atmegadci_device_isoc_fs_start(struct usb_xfer *xfer)
+{
+       /* start TD chain */
+       atmegadci_start_standard_chain(xfer);
+}
+
+struct usb_pipe_methods atmegadci_device_isoc_fs_methods =
+{
+       .open = atmegadci_device_isoc_fs_open,
+       .close = atmegadci_device_isoc_fs_close,
+       .enter = atmegadci_device_isoc_fs_enter,
+       .start = atmegadci_device_isoc_fs_start,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci root control support
+ *------------------------------------------------------------------------*
+ * Simulate a hardware HUB by handling all the necessary requests.
+ *------------------------------------------------------------------------*/
+
+static const struct usb_device_descriptor atmegadci_devd = {
+       .bLength = sizeof(struct usb_device_descriptor),
+       .bDescriptorType = UDESC_DEVICE,
+       .bcdUSB = {0x00, 0x02},
+       .bDeviceClass = UDCLASS_HUB,
+       .bDeviceSubClass = UDSUBCLASS_HUB,
+       .bDeviceProtocol = UDPROTO_FSHUB,
+       .bMaxPacketSize = 64,
+       .bcdDevice = {0x00, 0x01},
+       .iManufacturer = 1,
+       .iProduct = 2,
+       .bNumConfigurations = 1,
+};
+
+static const struct atmegadci_config_desc atmegadci_confd = {
+       .confd = {
+               .bLength = sizeof(struct usb_config_descriptor),
+               .bDescriptorType = UDESC_CONFIG,
+               .wTotalLength[0] = sizeof(atmegadci_confd),
+               .bNumInterface = 1,
+               .bConfigurationValue = 1,
+               .iConfiguration = 0,
+               .bmAttributes = UC_SELF_POWERED,
+               .bMaxPower = 0,
+       },
+       .ifcd = {
+               .bLength = sizeof(struct usb_interface_descriptor),
+               .bDescriptorType = UDESC_INTERFACE,
+               .bNumEndpoints = 1,
+               .bInterfaceClass = UICLASS_HUB,
+               .bInterfaceSubClass = UISUBCLASS_HUB,
+               .bInterfaceProtocol = 0,
+       },
+       .endpd = {
+               .bLength = sizeof(struct usb_endpoint_descriptor),
+               .bDescriptorType = UDESC_ENDPOINT,
+               .bEndpointAddress = (UE_DIR_IN | ATMEGA_INTR_ENDPT),
+               .bmAttributes = UE_INTERRUPT,
+               .wMaxPacketSize[0] = 8,
+               .bInterval = 255,
+       },
+};
+
+static const struct usb_hub_descriptor_min atmegadci_hubd = {
+       .bDescLength = sizeof(atmegadci_hubd),
+       .bDescriptorType = UDESC_HUB,
+       .bNbrPorts = 1,
+       .wHubCharacteristics[0] =
+       (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF,
+       .wHubCharacteristics[1] =
+       (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 8,
+       .bPwrOn2PwrGood = 50,
+       .bHubContrCurrent = 0,
+       .DeviceRemovable = {0},         /* port is removable */
+};
+
+#define        STRING_LANG \
+  0x09, 0x04,                          /* American English */
+
+#define        STRING_VENDOR \
+  'A', 0, 'T', 0, 'M', 0, 'E', 0, 'G', 0, 'A', 0
+
+#define        STRING_PRODUCT \
+  'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \
+  'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \
+  'U', 0, 'B', 0,
+
+USB_MAKE_STRING_DESC(STRING_LANG, atmegadci_langtab);
+USB_MAKE_STRING_DESC(STRING_VENDOR, atmegadci_vendor);
+USB_MAKE_STRING_DESC(STRING_PRODUCT, atmegadci_product);
+
+static usb_error_t
+atmegadci_roothub_exec(struct usb_device *udev,
+    struct usb_device_request *req, const void **pptr, uint16_t *plength)
+{
+       struct atmegadci_softc *sc = ATMEGA_BUS2SC(udev->bus);
+       const void *ptr;
+       uint16_t len;
+       uint16_t value;
+       uint16_t index;
+       uint8_t temp;
+       usb_error_t err;
+
+       USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+       /* buffer reset */
+       ptr = (const void *)&sc->sc_hub_temp;
+       len = 0;
+       err = 0;
+
+       value = UGETW(req->wValue);
+       index = UGETW(req->wIndex);
+
+       /* demultiplex the control request */
+
+       switch (req->bmRequestType) {
+       case UT_READ_DEVICE:
+               switch (req->bRequest) {
+               case UR_GET_DESCRIPTOR:
+                       goto tr_handle_get_descriptor;
+               case UR_GET_CONFIG:
+                       goto tr_handle_get_config;
+               case UR_GET_STATUS:
+                       goto tr_handle_get_status;
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_WRITE_DEVICE:
+               switch (req->bRequest) {
+               case UR_SET_ADDRESS:
+                       goto tr_handle_set_address;
+               case UR_SET_CONFIG:
+                       goto tr_handle_set_config;
+               case UR_CLEAR_FEATURE:
+                       goto tr_valid;  /* nop */
+               case UR_SET_DESCRIPTOR:
+                       goto tr_valid;  /* nop */
+               case UR_SET_FEATURE:
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_WRITE_ENDPOINT:
+               switch (req->bRequest) {
+               case UR_CLEAR_FEATURE:
+                       switch (UGETW(req->wValue)) {
+                       case UF_ENDPOINT_HALT:
+                               goto tr_handle_clear_halt;
+                       case UF_DEVICE_REMOTE_WAKEUP:
+                               goto tr_handle_clear_wakeup;
+                       default:
+                               goto tr_stalled;
+                       }
+                       break;
+               case UR_SET_FEATURE:
+                       switch (UGETW(req->wValue)) {
+                       case UF_ENDPOINT_HALT:
+                               goto tr_handle_set_halt;
+                       case UF_DEVICE_REMOTE_WAKEUP:
+                               goto tr_handle_set_wakeup;
+                       default:
+                               goto tr_stalled;
+                       }
+                       break;
+               case UR_SYNCH_FRAME:
+                       goto tr_valid;  /* nop */
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_READ_ENDPOINT:
+               switch (req->bRequest) {
+               case UR_GET_STATUS:
+                       goto tr_handle_get_ep_status;
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_WRITE_INTERFACE:
+               switch (req->bRequest) {
+               case UR_SET_INTERFACE:
+                       goto tr_handle_set_interface;
+               case UR_CLEAR_FEATURE:
+                       goto tr_valid;  /* nop */
+               case UR_SET_FEATURE:
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_READ_INTERFACE:
+               switch (req->bRequest) {
+               case UR_GET_INTERFACE:
+                       goto tr_handle_get_interface;
+               case UR_GET_STATUS:
+                       goto tr_handle_get_iface_status;
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_WRITE_CLASS_INTERFACE:
+       case UT_WRITE_VENDOR_INTERFACE:
+               /* XXX forward */
+               break;
+
+       case UT_READ_CLASS_INTERFACE:
+       case UT_READ_VENDOR_INTERFACE:
+               /* XXX forward */
+               break;
+
+       case UT_WRITE_CLASS_DEVICE:
+               switch (req->bRequest) {
+               case UR_CLEAR_FEATURE:
+                       goto tr_valid;
+               case UR_SET_DESCRIPTOR:
+               case UR_SET_FEATURE:
+                       break;
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_WRITE_CLASS_OTHER:
+               switch (req->bRequest) {
+               case UR_CLEAR_FEATURE:
+                       goto tr_handle_clear_port_feature;
+               case UR_SET_FEATURE:
+                       goto tr_handle_set_port_feature;
+               case UR_CLEAR_TT_BUFFER:
+               case UR_RESET_TT:
+               case UR_STOP_TT:
+                       goto tr_valid;
+
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_READ_CLASS_OTHER:
+               switch (req->bRequest) {
+               case UR_GET_TT_STATE:
+                       goto tr_handle_get_tt_state;
+               case UR_GET_STATUS:
+                       goto tr_handle_get_port_status;
+               default:
+                       goto tr_stalled;
+               }
+               break;
+
+       case UT_READ_CLASS_DEVICE:
+               switch (req->bRequest) {
+               case UR_GET_DESCRIPTOR:
+                       goto tr_handle_get_class_descriptor;
+               case UR_GET_STATUS:
+                       goto tr_handle_get_class_status;
+
+               default:
+                       goto tr_stalled;
+               }
+               break;
+       default:
+               goto tr_stalled;
+       }
+       goto tr_valid;
+
+tr_handle_get_descriptor:
+       switch (value >> 8) {
+       case UDESC_DEVICE:
+               if (value & 0xff) {
+                       goto tr_stalled;
+               }
+               len = sizeof(atmegadci_devd);
+               ptr = (const void *)&atmegadci_devd;
+               goto tr_valid;
+       case UDESC_CONFIG:
+               if (value & 0xff) {
+                       goto tr_stalled;
+               }
+               len = sizeof(atmegadci_confd);
+               ptr = (const void *)&atmegadci_confd;
+               goto tr_valid;
+       case UDESC_STRING:
+               switch (value & 0xff) {
+               case 0:         /* Language table */
+                       len = sizeof(atmegadci_langtab);
+                       ptr = (const void *)&atmegadci_langtab;
+                       goto tr_valid;
+
+               case 1:         /* Vendor */
+                       len = sizeof(atmegadci_vendor);
+                       ptr = (const void *)&atmegadci_vendor;
+                       goto tr_valid;
+
+               case 2:         /* Product */
+                       len = sizeof(atmegadci_product);
+                       ptr = (const void *)&atmegadci_product;
+                       goto tr_valid;
+               default:
+                       break;
+               }
+               break;
+       default:
+               goto tr_stalled;
+       }
+       goto tr_stalled;
+
+tr_handle_get_config:
+       len = 1;
+       sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+       goto tr_valid;
+
+tr_handle_get_status:
+       len = 2;
+       USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED);
+       goto tr_valid;
+
+tr_handle_set_address:
+       if (value & 0xFF00) {
+               goto tr_stalled;
+       }
+       sc->sc_rt_addr = value;
+       goto tr_valid;
+
+tr_handle_set_config:
+       if (value >= 2) {
+               goto tr_stalled;
+       }
+       sc->sc_conf = value;
+       goto tr_valid;
+
+tr_handle_get_interface:
+       len = 1;
+       sc->sc_hub_temp.wValue[0] = 0;
+       goto tr_valid;
+
+tr_handle_get_tt_state:
+tr_handle_get_class_status:
+tr_handle_get_iface_status:
+tr_handle_get_ep_status:
+       len = 2;
+       USETW(sc->sc_hub_temp.wValue, 0);
+       goto tr_valid;
+
+tr_handle_set_halt:
+tr_handle_set_interface:
+tr_handle_set_wakeup:
+tr_handle_clear_wakeup:
+tr_handle_clear_halt:
+       goto tr_valid;
+
+tr_handle_clear_port_feature:
+       if (index != 1) {
+               goto tr_stalled;
+       }
+       DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+
+       switch (value) {
+       case UHF_PORT_SUSPEND:
+               atmegadci_wakeup_peer(sc);
+               break;
+
+       case UHF_PORT_ENABLE:
+               sc->sc_flags.port_enabled = 0;
+               break;
+
+       case UHF_PORT_TEST:
+       case UHF_PORT_INDICATOR:
+       case UHF_C_PORT_ENABLE:
+       case UHF_C_PORT_OVER_CURRENT:
+       case UHF_C_PORT_RESET:
+               /* nops */
+               break;
+       case UHF_PORT_POWER:
+               sc->sc_flags.port_powered = 0;
+               atmegadci_pull_down(sc);
+               atmegadci_clocks_off(sc);
+               break;
+       case UHF_C_PORT_CONNECTION:
+               /* clear connect change flag */
+               sc->sc_flags.change_connect = 0;
+
+               if (!sc->sc_flags.status_bus_reset) {
+                       /* we are not connected */
+                       break;
+               }
+
+               /* configure the control endpoint */
+
+               /* select endpoint number */
+               ATMEGA_WRITE_1(sc, ATMEGA_UENUM, 0);
+
+               /* set endpoint reset */
+               ATMEGA_WRITE_1(sc, ATMEGA_UERST, ATMEGA_UERST_MASK(0));
+
+               /* clear endpoint reset */
+               ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0);
+
+               /* enable and stall endpoint */
+               ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+                   ATMEGA_UECONX_EPEN |
+                   ATMEGA_UECONX_STALLRQ);
+
+               /* one bank, 64-bytes wMaxPacket */
+               ATMEGA_WRITE_1(sc, ATMEGA_UECFG0X,
+                   ATMEGA_UECFG0X_EPTYPE0);
+               ATMEGA_WRITE_1(sc, ATMEGA_UECFG1X,
+                   ATMEGA_UECFG1X_ALLOC |
+                   ATMEGA_UECFG1X_EPBK0 |
+                   ATMEGA_UECFG1X_EPSIZE(3));
+
+               /* check valid config */
+               temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X);
+               if (!(temp & ATMEGA_UESTA0X_CFGOK)) {
+                       device_printf(sc->sc_bus.bdev,
+                           "Chip rejected EP0 configuration\n");
+               }
+               break;
+       case UHF_C_PORT_SUSPEND:
+               sc->sc_flags.change_suspend = 0;
+               break;
+       default:
+               err = USB_ERR_IOERROR;
+               goto done;
+       }
+       goto tr_valid;
+
+tr_handle_set_port_feature:
+       if (index != 1) {
+               goto tr_stalled;
+       }
+       DPRINTFN(9, "UR_SET_PORT_FEATURE\n");
+
+       switch (value) {
+       case UHF_PORT_ENABLE:
+               sc->sc_flags.port_enabled = 1;
+               break;
+       case UHF_PORT_SUSPEND:
+       case UHF_PORT_RESET:
+       case UHF_PORT_TEST:
+       case UHF_PORT_INDICATOR:
+               /* nops */
+               break;
+       case UHF_PORT_POWER:
+               sc->sc_flags.port_powered = 1;
+               break;
+       default:
+               err = USB_ERR_IOERROR;
+               goto done;
+       }
+       goto tr_valid;
+
+tr_handle_get_port_status:
+
+       DPRINTFN(9, "UR_GET_PORT_STATUS\n");
+
+       if (index != 1) {
+               goto tr_stalled;
+       }
+       if (sc->sc_flags.status_vbus) {
+               atmegadci_clocks_on(sc);
+               atmegadci_pull_up(sc);
+       } else {
+               atmegadci_pull_down(sc);
+               atmegadci_clocks_off(sc);
+       }
+
+       /* Select FULL-speed and Device Side Mode */
+
+       value = UPS_PORT_MODE_DEVICE;
+
+       if (sc->sc_flags.port_powered) {
+               value |= UPS_PORT_POWER;
+       }
+       if (sc->sc_flags.port_enabled) {
+               value |= UPS_PORT_ENABLED;
+       }
+       if (sc->sc_flags.status_vbus &&
+           sc->sc_flags.status_bus_reset) {
+               value |= UPS_CURRENT_CONNECT_STATUS;
+       }
+       if (sc->sc_flags.status_suspend) {
+               value |= UPS_SUSPEND;
+       }
+       USETW(sc->sc_hub_temp.ps.wPortStatus, value);
+
+       value = 0;
+
+       if (sc->sc_flags.change_connect) {
+               value |= UPS_C_CONNECT_STATUS;
+       }
+       if (sc->sc_flags.change_suspend) {
+               value |= UPS_C_SUSPEND;
+       }
+       USETW(sc->sc_hub_temp.ps.wPortChange, value);
+       len = sizeof(sc->sc_hub_temp.ps);
+       goto tr_valid;
+
+tr_handle_get_class_descriptor:
+       if (value & 0xFF) {
+               goto tr_stalled;
+       }
+       ptr = (const void *)&atmegadci_hubd;
+       len = sizeof(atmegadci_hubd);
+       goto tr_valid;
+
+tr_stalled:
+       err = USB_ERR_STALLED;
+tr_valid:
+done:
+       *plength = len;
+       *pptr = ptr;
+       return (err);
+}
+
+static void
+atmegadci_xfer_setup(struct usb_setup_params *parm)
+{
+       const struct usb_hw_ep_profile *pf;
+       struct atmegadci_softc *sc;
+       struct usb_xfer *xfer;
+       void *last_obj;
+       uint32_t ntd;
+       uint32_t n;
+       uint8_t ep_no;
+
+       sc = ATMEGA_BUS2SC(parm->udev->bus);
+       xfer = parm->curr_xfer;
+
+       /*
+        * NOTE: This driver does not use any of the parameters that
+        * are computed from the following values. Just set some
+        * reasonable dummies:
+        */
+       parm->hc_max_packet_size = 0x500;
+       parm->hc_max_packet_count = 1;
+       parm->hc_max_frame_size = 0x500;
+
+       usbd_transfer_setup_sub(parm);
+
+       /*
+        * compute maximum number of TDs
+        */
+       if ((xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) {
+
+               ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */
+                   + 1 /* SYNC 2 */ ;
+       } else {
+
+               ntd = xfer->nframes + 1 /* SYNC */ ;
+       }
+
+       /*
+        * check if "usbd_transfer_setup_sub" set an error
+        */
+       if (parm->err)
+               return;
+
+       /*
+        * allocate transfer descriptors
+        */
+       last_obj = NULL;
+
+       /*
+        * get profile stuff
+        */
+       ep_no = xfer->endpointno & UE_ADDR;
+       atmegadci_get_hw_ep_profile(parm->udev, &pf, ep_no);
+
+       if (pf == NULL) {
+               /* should not happen */
+               parm->err = USB_ERR_INVAL;
+               return;
+       }
+
+       /* align data */
+       parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+       for (n = 0; n != ntd; n++) {
+
+               struct atmegadci_td *td;
+
+               if (parm->buf) {
+
+                       td = USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+                       /* init TD */
+                       td->max_packet_size = xfer->max_packet_size;
+                       td->ep_no = ep_no;
+                       if (pf->support_multi_buffer) {
+                               td->support_multi_buffer = 1;
+                       }
+                       td->obj_next = last_obj;
+
+                       last_obj = td;
+               }
+               parm->size[0] += sizeof(*td);
+       }
+
+       xfer->td_start[0] = last_obj;
+}
+
+static void
+atmegadci_xfer_unsetup(struct usb_xfer *xfer)
+{
+       return;
+}
+
+static void
+atmegadci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
+    struct usb_endpoint *ep)
+{
+       struct atmegadci_softc *sc = ATMEGA_BUS2SC(udev->bus);
+
+       DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d,%d)\n",
+           ep, udev->address,
+           edesc->bEndpointAddress, udev->flags.usb_mode,
+           sc->sc_rt_addr, udev->device_index);
+
+       if (udev->device_index != sc->sc_rt_addr) {
+
+               if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+                       /* not supported */
+                       return;
+               }
+               if (udev->speed != USB_SPEED_FULL) {
+                       /* not supported */
+                       return;
+               }
+               if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS)
+                       ep->methods = &atmegadci_device_isoc_fs_methods;
+               else
+                       ep->methods = &atmegadci_device_non_isoc_methods;
+       }
+}
+
+static void
+atmegadci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
+{
+       struct atmegadci_softc *sc = ATMEGA_BUS2SC(bus);
+
+       switch (state) {
+       case USB_HW_POWER_SUSPEND:
+               atmegadci_suspend(sc);
+               break;
+       case USB_HW_POWER_SHUTDOWN:
+               atmegadci_uninit(sc);
+               break;
+       case USB_HW_POWER_RESUME:
+               atmegadci_resume(sc);
+               break;
+       default:
+               break;
+       }
+}
+
+struct usb_bus_methods atmegadci_bus_methods =
+{
+       .endpoint_init = &atmegadci_ep_init,
+       .xfer_setup = &atmegadci_xfer_setup,
+       .xfer_unsetup = &atmegadci_xfer_unsetup,
+       .get_hw_ep_profile = &atmegadci_get_hw_ep_profile,
+       .set_stall = &atmegadci_set_stall,
+       .clear_stall = &atmegadci_clear_stall,
+       .roothub_exec = &atmegadci_roothub_exec,
+       .xfer_poll = &atmegadci_do_poll,
+       .set_hw_power_sleep = &atmegadci_set_hw_power_sleep,
+};
diff --git a/sys/bus/u4b/controller/atmegadci.h b/sys/bus/u4b/controller/atmegadci.h
new file mode 100644 (file)
index 0000000..91ba030
--- /dev/null
@@ -0,0 +1,283 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2009 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB Device Port register definitions, copied from ATMEGA documentation
+ * provided by ATMEL.
+ */
+
+#ifndef _ATMEGADCI_H_
+#define        _ATMEGADCI_H_
+
+#define        ATMEGA_MAX_DEVICES (USB_MIN_DEVICES + 1)
+
+#define        ATMEGA_OTGTCON 0xF9
+#define        ATMEGA_OTGTCON_VALUE(x) ((x) << 0)
+#define        ATMEGA_OTGTCON_PAGE(x) ((x) << 5)
+
+#define        ATMEGA_UEINT 0xF4
+#define        ATMEGA_UEINT_MASK(n) (1 << (n)) /* endpoint interrupt mask */
+
+#define        ATMEGA_UEBCHX 0xF3              /* FIFO byte count high */
+#define        ATMEGA_UEBCLX 0xF2              /* FIFO byte count low */
+#define        ATMEGA_UEDATX 0xF1              /* FIFO data */
+
+#define        ATMEGA_UEIENX 0xF0              /* interrupt enable register */
+#define        ATMEGA_UEIENX_TXINE (1 << 0)
+#define        ATMEGA_UEIENX_STALLEDE (1 << 1)
+#define        ATMEGA_UEIENX_RXOUTE (1 << 2)
+#define        ATMEGA_UEIENX_RXSTPE (1 << 3)   /* received SETUP packet */
+#define        ATMEGA_UEIENX_NAKOUTE (1 << 4)
+#define        ATMEGA_UEIENX_NAKINE (1 << 6)
+#define        ATMEGA_UEIENX_FLERRE (1 << 7)
+
+#define        ATMEGA_UESTA1X 0xEF
+#define        ATMEGA_UESTA1X_CURRBK (3 << 0)  /* current bank */
+#define        ATMEGA_UESTA1X_CTRLDIR (1 << 2) /* control endpoint direction */
+
+#define        ATMEGA_UESTA0X 0xEE
+#define        ATMEGA_UESTA0X_NBUSYBK (3 << 0)
+#define        ATMEGA_UESTA0X_DTSEQ (3 << 2)
+#define        ATMEGA_UESTA0X_UNDERFI (1 << 5) /* underflow */
+#define        ATMEGA_UESTA0X_OVERFI (1 << 6)  /* overflow */
+#define        ATMEGA_UESTA0X_CFGOK (1 << 7)
+
+#define        ATMEGA_UECFG1X 0xED             /* endpoint config register */
+#define        ATMEGA_UECFG1X_ALLOC (1 << 1)
+#define        ATMEGA_UECFG1X_EPBK0 (0 << 2)
+#define        ATMEGA_UECFG1X_EPBK1 (1 << 2)
+#define        ATMEGA_UECFG1X_EPBK2 (2 << 2)
+#define        ATMEGA_UECFG1X_EPBK3 (3 << 2)
+#define        ATMEGA_UECFG1X_EPSIZE(n) ((n) << 4)
+
+#define        ATMEGA_UECFG0X 0xEC
+#define        ATMEGA_UECFG0X_EPDIR (1 << 0)   /* endpoint direction */
+#define        ATMEGA_UECFG0X_EPTYPE0 (0 << 6)
+#define        ATMEGA_UECFG0X_EPTYPE1 (1 << 6)
+#define        ATMEGA_UECFG0X_EPTYPE2 (2 << 6)
+#define        ATMEGA_UECFG0X_EPTYPE3 (3 << 6)
+
+#define        ATMEGA_UECONX 0xEB
+#define        ATMEGA_UECONX_EPEN (1 << 0)
+#define        ATMEGA_UECONX_RSTDT (1 << 3)
+#define        ATMEGA_UECONX_STALLRQC (1 << 4) /* stall request clear */
+#define        ATMEGA_UECONX_STALLRQ (1 << 5)  /* stall request set */
+
+#define        ATMEGA_UERST 0xEA               /* endpoint reset register */
+#define        ATMEGA_UERST_MASK(n) (1 << (n))
+
+#define        ATMEGA_UENUM 0xE9               /* endpoint number */
+
+#define        ATMEGA_UEINTX 0xE8              /* interrupt register */
+#define        ATMEGA_UEINTX_TXINI (1 << 0)
+#define        ATMEGA_UEINTX_STALLEDI (1 << 1)
+#define        ATMEGA_UEINTX_RXOUTI (1 << 2)
+#define        ATMEGA_UEINTX_RXSTPI (1 << 3)   /* received setup packet */
+#define        ATMEGA_UEINTX_NAKOUTI (1 << 4)
+#define        ATMEGA_UEINTX_RWAL (1 << 5)
+#define        ATMEGA_UEINTX_NAKINI (1 << 6)
+#define        ATMEGA_UEINTX_FIFOCON (1 << 7)
+
+#define        ATMEGA_UDMFN 0xE6
+#define        ATMEGA_UDMFN_FNCERR (1 << 4)
+
+#define        ATMEGA_UDFNUMH 0xE5             /* frame number high */
+#define        ATMEGA_UDFNUMH_MASK 7
+
+#define        ATMEGA_UDFNUML 0xE4             /* frame number low */
+#define        ATMEGA_UDFNUML_MASK 0xFF
+
+#define        ATMEGA_FRAME_MASK 0x7FF
+
+#define        ATMEGA_UDADDR 0xE3              /* USB address */
+#define        ATMEGA_UDADDR_MASK 0x7F
+#define        ATMEGA_UDADDR_ADDEN (1 << 7)
+
+#define        ATMEGA_UDIEN 0xE2               /* USB device interrupt enable */
+#define        ATMEGA_UDINT_SUSPE (1 << 0)
+#define        ATMEGA_UDINT_MSOFE (1 << 1)
+#define        ATMEGA_UDINT_SOFE (1 << 2)
+#define        ATMEGA_UDINT_EORSTE (1 << 3)
+#define        ATMEGA_UDINT_WAKEUPE (1 << 4)
+#define        ATMEGA_UDINT_EORSME (1 << 5)
+#define        ATMEGA_UDINT_UPRSME (1 << 6)
+
+#define        ATMEGA_UDINT 0xE1               /* USB device interrupt status */
+#define        ATMEGA_UDINT_SUSPI (1 << 0)
+#define        ATMEGA_UDINT_MSOFI (1 << 1)
+#define        ATMEGA_UDINT_SOFI (1 << 2)
+#define        ATMEGA_UDINT_EORSTI (1 << 3)
+#define        ATMEGA_UDINT_WAKEUPI (1 << 4)
+#define        ATMEGA_UDINT_EORSMI (1 << 5)
+#define        ATMEGA_UDINT_UPRSMI (1 << 6)
+
+#define        ATMEGA_UDCON 0xE0               /* USB device connection register */
+#define        ATMEGA_UDCON_DETACH (1 << 0)
+#define        ATMEGA_UDCON_RMWKUP (1 << 1)
+#define        ATMEGA_UDCON_LSM (1 << 2)
+#define        ATMEGA_UDCON_RSTCPU (1 << 3)
+
+#define        ATMEGA_OTGINT 0xDF
+
+#define        ATMEGA_OTGCON 0xDD
+#define        ATMEGA_OTGCON_VBUSRQC (1 << 0)
+#define        ATMEGA_OTGCON_VBUSREQ (1 << 1)
+#define        ATMEGA_OTGCON_VBUSHWC (1 << 2)
+#define        ATMEGA_OTGCON_SRPSEL (1 << 3)
+#define        ATMEGA_OTGCON_SRPREQ (1 << 4)
+#define        ATMEGA_OTGCON_HNPREQ (1 << 5)
+
+#define        ATMEGA_USBINT 0xDA
+#define        ATMEGA_USBINT_VBUSTI (1 << 0)   /* USB VBUS interrupt */
+#define        ATMEGA_USBINT_IDI (1 << 1)      /* USB ID interrupt */
+
+#define        ATMEGA_USBSTA 0xD9
+#define        ATMEGA_USBSTA_VBUS (1 << 0)
+#define        ATMEGA_USBSTA_ID (1 << 1)
+
+#define        ATMEGA_USBCON 0xD8
+#define        ATMEGA_USBCON_VBUSTE (1 << 0)
+#define        ATMEGA_USBCON_IDE (1 << 1)
+#define        ATMEGA_USBCON_OTGPADE (1 << 4)
+#define        ATMEGA_USBCON_FRZCLK (1 << 5)
+#define        ATMEGA_USBCON_USBE (1 << 7)
+
+#define        ATMEGA_UHWCON 0xD7
+#define        ATMEGA_UHWCON_UVREGE (1 << 0)
+#define        ATMEGA_UHWCON_UVCONE (1 << 4)
+#define        ATMEGA_UHWCON_UIDE (1 << 6)
+#define        ATMEGA_UHWCON_UIMOD (1 << 7)
+
+#define        ATMEGA_READ_1(sc, reg) \
+  bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define        ATMEGA_WRITE_1(sc, reg, data) \
+  bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+#define        ATMEGA_WRITE_MULTI_1(sc, reg, ptr, len) \
+  bus_space_write_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, ptr, len)
+
+#define        ATMEGA_READ_MULTI_1(sc, reg, ptr, len) \
+  bus_space_read_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, ptr, len)
+
+/*
+ * Maximum number of endpoints supported:
+ */
+#define        ATMEGA_EP_MAX 7
+
+struct atmegadci_td;
+
+typedef uint8_t (atmegadci_cmd_t)(struct atmegadci_td *td);
+typedef void (atmegadci_clocks_t)(struct usb_bus *);
+
+struct atmegadci_td {
+       struct atmegadci_td *obj_next;
+       atmegadci_cmd_t *func;
+       struct usb_page_cache *pc;
+       uint32_t offset;
+       uint32_t remainder;
+       uint16_t max_packet_size;
+       uint8_t error:1;
+       uint8_t alt_next:1;
+       uint8_t short_pkt:1;
+       uint8_t support_multi_buffer:1;
+       uint8_t did_stall:1;
+       uint8_t ep_no:3;
+};
+
+struct atmegadci_std_temp {
+       atmegadci_cmd_t *func;
+       struct usb_page_cache *pc;
+       struct atmegadci_td *td;
+       struct atmegadci_td *td_next;
+       uint32_t len;
+       uint32_t offset;
+       uint16_t max_frame_size;
+       uint8_t short_pkt;
+       /*
+         * short_pkt = 0: transfer should be short terminated
+         * short_pkt = 1: transfer should not be short terminated
+         */
+       uint8_t setup_alt_next;
+       uint8_t did_stall;
+};
+
+struct atmegadci_config_desc {
+       struct usb_config_descriptor confd;
+       struct usb_interface_descriptor ifcd;
+       struct usb_endpoint_descriptor endpd;
+} __packed;
+
+union atmegadci_hub_temp {
+       uWord   wValue;
+       struct usb_port_status ps;
+};
+
+struct atmegadci_flags {
+       uint8_t change_connect:1;
+       uint8_t change_suspend:1;
+       uint8_t status_suspend:1;       /* set if suspended */
+       uint8_t status_vbus:1;          /* set if present */
+       uint8_t status_bus_reset:1;     /* set if reset complete */
+       uint8_t remote_wakeup:1;
+       uint8_t self_powered:1;
+       uint8_t clocks_off:1;
+       uint8_t port_powered:1;
+       uint8_t port_enabled:1;
+       uint8_t d_pulled_up:1;
+};
+
+struct atmegadci_softc {
+       struct usb_bus sc_bus;
+       union atmegadci_hub_temp sc_hub_temp;
+
+       /* must be set by by the bus interface layer */
+       atmegadci_clocks_t *sc_clocks_on;
+       atmegadci_clocks_t *sc_clocks_off;
+
+       struct usb_device *sc_devices[ATMEGA_MAX_DEVICES];
+       struct resource *sc_irq_res;
+       void   *sc_intr_hdl;
+       struct resource *sc_io_res;
+       bus_space_tag_t sc_io_tag;
+       bus_space_handle_t sc_io_hdl;
+
+       uint8_t sc_rt_addr;             /* root hub address */
+       uint8_t sc_dv_addr;             /* device address */
+       uint8_t sc_conf;                /* root hub config */
+
+       uint8_t sc_hub_idata[1];
+
+       struct atmegadci_flags sc_flags;
+};
+
+/* prototypes */
+
+usb_error_t atmegadci_init(struct atmegadci_softc *sc);
+void   atmegadci_uninit(struct atmegadci_softc *sc);
+void   atmegadci_interrupt(struct atmegadci_softc *sc);
+
+#endif                                 /* _ATMEGADCI_H_ */
diff --git a/sys/bus/u4b/controller/atmegadci_atmelarm.c b/sys/bus/u4b/controller/atmegadci_atmelarm.c
new file mode 100644 (file)
index 0000000..6c380b6
--- /dev/null
@@ -0,0 +1,216 @@
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*-
+ * Copyright (c) 2009 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/atmegadci.h>
+
+#include <sys/rman.h>
+
+static device_probe_t atmegadci_probe;
+static device_attach_t atmegadci_attach;
+static device_detach_t atmegadci_detach;
+
+struct atmegadci_super_softc {
+       struct atmegadci_softc sc_otg;  /* must be first */
+};
+
+static void
+atmegadci_clocks_on(struct usb_bus *bus)
+{
+       /* TODO */
+}
+
+static void
+atmegadci_clocks_off(struct usb_bus *bus)
+{
+       /* TODO */
+}
+
+static int
+atmegadci_probe(device_t dev)
+{
+       device_set_desc(dev, "ATMEL OTG integrated USB controller");
+       return (0);
+}
+
+static int
+atmegadci_attach(device_t dev)
+{
+       struct atmegadci_super_softc *sc = device_get_softc(dev);
+       int err;
+       int rid;
+
+       /* setup MUSB OTG USB controller interface softc */
+       sc->sc_otg.sc_clocks_on = &atmegadci_clocks_on;
+       sc->sc_otg.sc_clocks_off = &atmegadci_clocks_off;
+
+       /* initialise some bus fields */
+       sc->sc_otg.sc_bus.parent = dev;
+       sc->sc_otg.sc_bus.devices = sc->sc_otg.sc_devices;
+       sc->sc_otg.sc_bus.devices_max = ATMEGA_MAX_DEVICES;
+
+       /* get all DMA memory */
+       if (usb_bus_mem_alloc_all(&sc->sc_otg.sc_bus,
+           USB_GET_DMA_TAG(dev), NULL)) {
+               return (ENOMEM);
+       }
+       rid = 0;
+       sc->sc_otg.sc_io_res =
+           bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+
+       if (!(sc->sc_otg.sc_io_res)) {
+               err = ENOMEM;
+               goto error;
+       }
+       sc->sc_otg.sc_io_tag = rman_get_bustag(sc->sc_otg.sc_io_res);
+       sc->sc_otg.sc_io_hdl = rman_get_bushandle(sc->sc_otg.sc_io_res);
+
+       rid = 0;
+       sc->sc_otg.sc_irq_res =
+           bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
+       if (!(sc->sc_otg.sc_irq_res)) {
+               goto error;
+       }
+       sc->sc_otg.sc_bus.bdev = device_add_child(dev, "usbus", -1);
+       if (!(sc->sc_otg.sc_bus.bdev)) {
+               goto error;
+       }
+       device_set_ivars(sc->sc_otg.sc_bus.bdev, &sc->sc_otg.sc_bus);
+
+       err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+           NULL, (driver_intr_t *)atmegadci_interrupt, sc, &sc->sc_otg.sc_intr_hdl);
+       if (err) {
+               sc->sc_otg.sc_intr_hdl = NULL;
+               goto error;
+       }
+       err = atmegadci_init(&sc->sc_otg);
+       if (!err) {
+               err = device_probe_and_attach(sc->sc_otg.sc_bus.bdev);
+       }
+       if (err) {
+               goto error;
+       }
+       return (0);
+
+error:
+       atmegadci_detach(dev);
+       return (ENXIO);
+}
+
+static int
+atmegadci_detach(device_t dev)
+{
+       struct atmegadci_super_softc *sc = device_get_softc(dev);
+       device_t bdev;
+       int err;
+
+       if (sc->sc_otg.sc_bus.bdev) {
+               bdev = sc->sc_otg.sc_bus.bdev;
+               device_detach(bdev);
+               device_delete_child(dev, bdev);
+       }
+       /* during module unload there are lots of children leftover */
+       device_delete_children(dev);
+
+       if (sc->sc_otg.sc_irq_res && sc->sc_otg.sc_intr_hdl) {
+               /*
+                * only call atmegadci_uninit() after atmegadci_init()
+                */
+               atmegadci_uninit(&sc->sc_otg);
+
+               err = bus_teardown_intr(dev, sc->sc_otg.sc_irq_res,
+                   sc->sc_otg.sc_intr_hdl);
+               sc->sc_otg.sc_intr_hdl = NULL;
+       }
+       /* free IRQ channel, if any */
+       if (sc->sc_otg.sc_irq_res) {
+               bus_release_resource(dev, SYS_RES_IRQ, 0,
+                   sc->sc_otg.sc_irq_res);
+               sc->sc_otg.sc_irq_res = NULL;
+       }
+       /* free memory resource, if any */
+       if (sc->sc_otg.sc_io_res) {
+               bus_release_resource(dev, SYS_RES_MEMORY, 0,
+                   sc->sc_otg.sc_io_res);
+               sc->sc_otg.sc_io_res = NULL;
+       }
+       usb_bus_mem_free_all(&sc->sc_otg.sc_bus, NULL);
+
+       return (0);
+}
+
+static device_method_t atmegadci_methods[] = {
+       /* Device interface */
+       DEVMETHOD(device_probe, atmegadci_probe),
+       DEVMETHOD(device_attach, atmegadci_attach),
+       DEVMETHOD(device_detach, atmegadci_detach),
+       DEVMETHOD(device_suspend, bus_generic_suspend),
+       DEVMETHOD(device_resume, bus_generic_resume),
+       DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+       DEVMETHOD_END
+};
+
+static driver_t atmegadci_driver = {
+       .name = "atmegadci",
+       .methods = atmegadci_methods,
+       .size = sizeof(struct atmegadci_super_softc),
+};
+
+static devclass_t atmegadci_devclass;
+
+DRIVER_MODULE(atmegadci, atmelarm, atmegadci_driver, atmegadci_devclass, 0, 0);
+MODULE_DEPEND(atmegadci, usb, 1, 1, 1);
diff --git a/sys/bus/u4b/controller/avr32dci.c b/sys/bus/u4b/controller/avr32dci.c
new file mode 100644 (file)
index 0000000..9494c30
--- /dev/null
@@ -0,0 +1,2105 @@
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*-
+ * Copyright (c) 2009 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the driver for the AVR32 series USB Device
+ * Controller
+ */
+
+/*
+ * NOTE: When the chip detects BUS-reset it will also reset the
+ * endpoints, Function-address and more.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define        USB_DEBUG_VAR avr32dci_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/avr32dci.h>
+
+#define        AVR32_BUS2SC(bus) \
+   ((struct avr32dci_softc *)(((uint8_t *)(bus)) - \
+    ((uint8_t *)&(((struct avr32dci_softc *)0)->sc_bus))))
+
+#define        AVR32_PC2SC(pc) \
+   AVR32_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus)
+
+#ifdef USB_DEBUG
+static int avr32dci_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, avr32dci, CTLFLAG_RW, 0, "USB AVR32 DCI");
+SYSCTL_INT(_hw_usb_avr32dci, OID_AUTO, debug, CTLFLAG_RW,
+    &avr32dci_debug, 0, "AVR32 DCI debug level");
+#endif
+
+#define        AVR32_INTR_ENDPT 1
+
+/* prototypes */
+
+struct usb_bus_methods avr32dci_bus_methods;
+struct usb_pipe_methods avr32dci_device_non_isoc_methods;
+struct usb_pipe_methods avr32dci_device_isoc_fs_methods;
+
+static avr32dci_cmd_t avr32dci_setup_rx;
+static avr32dci_cmd_t avr32dci_data_rx;
+static avr32dci_cmd_t avr32dci_data_tx;
+static avr32dci_cmd_t avr32dci_data_tx_sync;
+static void avr32dci_device_done(struct usb_xfer *, usb_error_t);
+static void avr32dci_do_poll(struct usb_bus *);
+static void avr32dci_standard_done(struct usb_xfer *);
+static void avr32dci_root_intr(struct avr32dci_softc *sc);
+
+/*
+ * Here is a list of what the chip supports:
+ */
+static const struct usb_hw_ep_profile
+       avr32dci_ep_profile[4] = {
+
+       [0] = {
+               .max_in_frame_size = 64,
+               .max_out_frame_size = 64,
+               .is_simplex = 1,
+               .support_control = 1,
+       },
+
+       [1] = {
+               .max_in_frame_size = 512,
+               .max_out_frame_size = 512,
+               .is_simplex = 1,
+               .support_bulk = 1,
+               .support_interrupt = 1,
+               .support_isochronous = 1,
+               .support_in = 1,
+               .support_out = 1,
+       },
+
+       [2] = {
+               .max_in_frame_size = 64,
+               .max_out_frame_size = 64,
+               .is_simplex = 1,
+               .support_bulk = 1,
+               .support_interrupt = 1,
+               .support_in = 1,
+               .support_out = 1,
+       },
+
+       [3] = {
+               .max_in_frame_size = 1024,
+               .max_out_frame_size = 1024,
+               .is_simplex = 1,
+               .support_bulk = 1,
+               .support_interrupt = 1,
+               .support_isochronous = 1,
+               .support_in = 1,
+               .support_out = 1,
+       },
+};
+
+static void
+avr32dci_get_hw_ep_profile(struct usb_device *udev,
+    const struct usb_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+       if (ep_addr == 0)
+               *ppf = avr32dci_ep_profile;
+       else if (ep_addr < 3)
+               *ppf = avr32dci_ep_profile + 1;
+       else if (ep_addr < 5)
+               *ppf = avr32dci_ep_profile + 2;
+       else if (ep_addr < 7)
+               *ppf = avr32dci_ep_profile + 3;
+       else
+               *ppf = NULL;
+}
+
+static void
+avr32dci_mod_ctrl(struct avr32dci_softc *sc, uint32_t set, uint32_t clear)
+{
+       uint32_t temp;
+
+       temp = AVR32_READ_4(sc, AVR32_CTRL);
+       temp |= set;
+       temp &= ~clear;
+       AVR32_WRITE_4(sc, AVR32_CTRL, temp);
+}
+
+static void
+avr32dci_mod_ien(struct avr32dci_softc *sc, uint32_t set, uint32_t clear)
+{
+       uint32_t temp;
+
+       temp = AVR32_READ_4(sc, AVR32_IEN);
+       temp |= set;
+       temp &= ~clear;
+       AVR32_WRITE_4(sc, AVR32_IEN, temp);
+}
+
+static void
+avr32dci_clocks_on(struct avr32dci_softc *sc)
+{
+       if (sc->sc_flags.clocks_off &&
+           sc->sc_flags.port_powered) {
+
+               DPRINTFN(5, "\n");
+
+               /* turn on clocks */
+               (sc->sc_clocks_on) (&sc->sc_bus);
+
+               avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_EN_USBA, 0);
+
+               sc->sc_flags.clocks_off = 0;
+       }
+}
+
+static void
+avr32dci_clocks_off(struct avr32dci_softc *sc)
+{
+       if (!sc->sc_flags.clocks_off) {
+
+               DPRINTFN(5, "\n");
+
+               avr32dci_mod_ctrl(sc, 0, AVR32_CTRL_DEV_EN_USBA);
+
+               /* turn clocks off */
+               (sc->sc_clocks_off) (&sc->sc_bus);
+
+               sc->sc_flags.clocks_off = 1;
+       }
+}
+
+static void
+avr32dci_pull_up(struct avr32dci_softc *sc)
+{
+       /* pullup D+, if possible */
+
+       if (!sc->sc_flags.d_pulled_up &&
+           sc->sc_flags.port_powered) {
+               sc->sc_flags.d_pulled_up = 1;
+               avr32dci_mod_ctrl(sc, 0, AVR32_CTRL_DEV_DETACH);
+       }
+}
+
+static void
+avr32dci_pull_down(struct avr32dci_softc *sc)
+{
+       /* pulldown D+, if possible */
+
+       if (sc->sc_flags.d_pulled_up) {
+               sc->sc_flags.d_pulled_up = 0;
+               avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_DETACH, 0);
+       }
+}
+
+static void
+avr32dci_wakeup_peer(struct avr32dci_softc *sc)
+{
+       if (!sc->sc_flags.status_suspend) {
+               return;
+       }
+       avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_REWAKEUP, 0);
+
+       /* wait 8 milliseconds */
+       /* Wait for reset to complete. */
+       usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+
+       /* hardware should have cleared RMWKUP bit */
+}
+
+static void
+avr32dci_set_address(struct avr32dci_softc *sc, uint8_t addr)
+{
+       DPRINTFN(5, "addr=%d\n", addr);
+
+       avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_FADDR_EN | addr, 0);
+}
+
+static uint8_t
+avr32dci_setup_rx(struct avr32dci_td *td)
+{
+       struct avr32dci_softc *sc;
+       struct usb_device_request req;
+       uint16_t count;
+       uint32_t temp;
+
+       /* get pointer to softc */
+       sc = AVR32_PC2SC(td->pc);
+
+       /* check endpoint status */
+       temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no));
+
+       DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp);
+
+       if (!(temp & AVR32_EPTSTA_RX_SETUP)) {
+               goto not_complete;
+       }
+       /* clear did stall */
+       td->did_stall = 0;
+       /* get the packet byte count */
+       count = AVR32_EPTSTA_BYTE_COUNT(temp);
+
+       /* verify data length */
+       if (count != td->remainder) {
+               DPRINTFN(0, "Invalid SETUP packet "
+                   "length, %d bytes\n", count);
+               goto not_complete;
+       }
+       if (count != sizeof(req)) {
+               DPRINTFN(0, "Unsupported SETUP packet "
+                   "length, %d bytes\n", count);
+               goto not_complete;
+       }
+       /* receive data */
+       memcpy(&req, sc->physdata, sizeof(req));
+
+       /* copy data into real buffer */
+       usbd_copy_in(td->pc, 0, &req, sizeof(req));
+
+       td->offset = sizeof(req);
+       td->remainder = 0;
+
+       /* sneak peek the set address */
+       if ((req.bmRequestType == UT_WRITE_DEVICE) &&
+           (req.bRequest == UR_SET_ADDRESS)) {
+               sc->sc_dv_addr = req.wValue[0] & 0x7F;
+               /* must write address before ZLP */
+               avr32dci_mod_ctrl(sc, 0, AVR32_CTRL_DEV_FADDR_EN |
+                   AVR32_CTRL_DEV_ADDR);
+               avr32dci_mod_ctrl(sc, sc->sc_dv_addr, 0);
+       } else {
+               sc->sc_dv_addr = 0xFF;
+       }
+
+       /* clear SETUP packet interrupt */
+       AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(td->ep_no), AVR32_EPTSTA_RX_SETUP);
+       return (0);                     /* complete */
+
+not_complete:
+       if (temp & AVR32_EPTSTA_RX_SETUP) {
+               /* clear SETUP packet interrupt */
+               AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(td->ep_no), AVR32_EPTSTA_RX_SETUP);
+       }
+       /* abort any ongoing transfer */
+       if (!td->did_stall) {
+               DPRINTFN(5, "stalling\n");
+               AVR32_WRITE_4(sc, AVR32_EPTSETSTA(td->ep_no),
+                   AVR32_EPTSTA_FRCESTALL);
+               td->did_stall = 1;
+       }
+       return (1);                     /* not complete */
+}
+
+static uint8_t
+avr32dci_data_rx(struct avr32dci_td *td)
+{
+       struct avr32dci_softc *sc;
+       struct usb_page_search buf_res;
+       uint16_t count;
+       uint32_t temp;
+       uint8_t to;
+       uint8_t got_short;
+
+       to = 4;                         /* don't loop forever! */
+       got_short = 0;
+
+       /* get pointer to softc */
+       sc = AVR32_PC2SC(td->pc);
+
+repeat:
+       /* check if any of the FIFO banks have data */
+       /* check endpoint status */
+       temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no));
+
+       DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp);
+
+       if (temp & AVR32_EPTSTA_RX_SETUP) {
+               if (td->remainder == 0) {
+                       /*
+                        * We are actually complete and have
+                        * received the next SETUP
+                        */
+                       DPRINTFN(5, "faking complete\n");
+                       return (0);     /* complete */
+               }
+               /*
+                * USB Host Aborted the transfer.
+                */
+               td->error = 1;
+               return (0);             /* complete */
+       }
+       /* check status */
+       if (!(temp & AVR32_EPTSTA_RX_BK_RDY)) {
+               /* no data */
+               goto not_complete;
+       }
+       /* get the packet byte count */
+       count = AVR32_EPTSTA_BYTE_COUNT(temp);
+
+       /* verify the packet byte count */
+       if (count != td->max_packet_size) {
+               if (count < td->max_packet_size) {
+                       /* we have a short packet */
+                       td->short_pkt = 1;
+                       got_short = 1;
+               } else {
+                       /* invalid USB packet */
+                       td->error = 1;
+                       return (0);     /* we are complete */
+               }
+       }
+       /* verify the packet byte count */
+       if (count > td->remainder) {
+               /* invalid USB packet */
+               td->error = 1;
+               return (0);             /* we are complete */
+       }
+       while (count > 0) {
+               usbd_get_page(td->pc, td->offset, &buf_res);
+
+               /* get correct length */
+               if (buf_res.length > count) {
+                       buf_res.length = count;
+               }
+               /* receive data */
+               memcpy(buf_res.buffer, sc->physdata +
+                   (AVR32_EPTSTA_CURRENT_BANK(temp) << td->bank_shift) +
+                   (td->ep_no << 16) + (td->offset % td->max_packet_size), buf_res.length);
+               /* update counters */
+               count -= buf_res.length;
+               td->offset += buf_res.length;
+               td->remainder -= buf_res.length;
+       }
+
+       /* clear OUT packet interrupt */
+       AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(td->ep_no), AVR32_EPTSTA_RX_BK_RDY);
+
+       /* check if we are complete */
+       if ((td->remainder == 0) || got_short) {
+               if (td->short_pkt) {
+                       /* we are complete */
+                       return (0);
+               }
+               /* else need to receive a zero length packet */
+       }
+       if (--to) {
+               goto repeat;
+       }
+not_complete:
+       return (1);                     /* not complete */
+}
+
+static uint8_t
+avr32dci_data_tx(struct avr32dci_td *td)
+{
+       struct avr32dci_softc *sc;
+       struct usb_page_search buf_res;
+       uint16_t count;
+       uint8_t to;
+       uint32_t temp;
+
+       to = 4;                         /* don't loop forever! */
+
+       /* get pointer to softc */
+       sc = AVR32_PC2SC(td->pc);
+
+repeat:
+
+       /* check endpoint status */
+       temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no));
+
+       DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp);
+
+       if (temp & AVR32_EPTSTA_RX_SETUP) {
+               /*
+                * The current transfer was aborted
+                * by the USB Host
+                */
+               td->error = 1;
+               return (0);             /* complete */
+       }
+       if (temp & AVR32_EPTSTA_TX_PK_RDY) {
+               /* cannot write any data - all banks are busy */
+               goto not_complete;
+       }
+       count = td->max_packet_size;
+       if (td->remainder < count) {
+               /* we have a short packet */
+               td->short_pkt = 1;
+               count = td->remainder;
+       }
+       while (count > 0) {
+
+               usbd_get_page(td->pc, td->offset, &buf_res);
+
+               /* get correct length */
+               if (buf_res.length > count) {
+                       buf_res.length = count;
+               }
+               /* transmit data */
+               memcpy(sc->physdata +
+                   (AVR32_EPTSTA_CURRENT_BANK(temp) << td->bank_shift) +
+                   (td->ep_no << 16) + (td->offset % td->max_packet_size),
+                   buf_res.buffer, buf_res.length);
+               /* update counters */
+               count -= buf_res.length;
+               td->offset += buf_res.length;
+               td->remainder -= buf_res.length;
+       }
+
+       /* allocate FIFO bank */
+       AVR32_WRITE_4(sc, AVR32_EPTCTL(td->ep_no), AVR32_EPTCTL_TX_PK_RDY);
+
+       /* check remainder */
+       if (td->remainder == 0) {
+               if (td->short_pkt) {
+                       return (0);     /* complete */
+               }
+               /* else we need to transmit a short packet */
+       }
+       if (--to) {
+               goto repeat;
+       }
+not_complete:
+       return (1);                     /* not complete */
+}
+
+static uint8_t
+avr32dci_data_tx_sync(struct avr32dci_td *td)
+{
+       struct avr32dci_softc *sc;
+       uint32_t temp;
+
+       /* get pointer to softc */
+       sc = AVR32_PC2SC(td->pc);
+
+       /* check endpoint status */
+       temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no));
+
+       DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp);
+
+       if (temp & AVR32_EPTSTA_RX_SETUP) {
+               DPRINTFN(5, "faking complete\n");
+               /* Race condition */
+               return (0);             /* complete */
+       }
+       /*
+        * The control endpoint has only got one bank, so if that bank
+        * is free the packet has been transferred!
+        */
+       if (AVR32_EPTSTA_BUSY_BANK_STA(temp) != 0) {
+               /* cannot write any data - a bank is busy */
+               goto not_complete;
+       }
+       if (sc->sc_dv_addr != 0xFF) {
+               /* set new address */
+               avr32dci_set_address(sc, sc->sc_dv_addr);
+       }
+       return (0);                     /* complete */
+
+not_complete:
+       return (1);                     /* not complete */
+}
+
+static uint8_t
+avr32dci_xfer_do_fifo(struct usb_xfer *xfer)
+{
+       struct avr32dci_td *td;
+
+       DPRINTFN(9, "\n");
+
+       td = xfer->td_transfer_cache;
+       while (1) {
+               if ((td->func) (td)) {
+                       /* operation in progress */
+                       break;
+               }
+               if (((void *)td) == xfer->td_transfer_last) {
+                       goto done;
+               }
+               if (td->error) {
+                       goto done;
+               } else if (td->remainder > 0) {
+                       /*
+                        * We had a short transfer. If there is no alternate
+                        * next, stop processing !
+                        */
+                       if (!td->alt_next) {
+                               goto done;
+                       }
+               }
+               /*
+                * Fetch the next transfer descriptor and transfer
+                * some flags to the next transfer descriptor
+                */
+               td = td->obj_next;
+               xfer->td_transfer_cache = td;
+       }
+       return (1);                     /* not complete */
+
+done:
+       /* compute all actual lengths */
+
+       avr32dci_standard_done(xfer);
+       return (0);                     /* complete */
+}
+
+static void
+avr32dci_interrupt_poll(struct avr32dci_softc *sc)
+{
+       struct usb_xfer *xfer;
+
+repeat:
+       TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+               if (!avr32dci_xfer_do_fifo(xfer)) {
+                       /* queue has been modified */
+                       goto repeat;
+               }
+       }
+}
+
+void
+avr32dci_vbus_interrupt(struct avr32dci_softc *sc, uint8_t is_on)
+{
+       DPRINTFN(5, "vbus = %u\n", is_on);
+
+       if (is_on) {
+               if (!sc->sc_flags.status_vbus) {
+                       sc->sc_flags.status_vbus = 1;
+
+                       /* complete root HUB interrupt endpoint */
+
+                       avr32dci_root_intr(sc);
+               }
+       } else {
+               if (sc->sc_flags.status_vbus) {
+                       sc->sc_flags.status_vbus = 0;
+                       sc->sc_flags.status_bus_reset = 0;
+                       sc->sc_flags.status_suspend = 0;
+                       sc->sc_flags.change_suspend = 0;
+                       sc->sc_flags.change_connect = 1;
+
+                       /* complete root HUB interrupt endpoint */
+
+                       avr32dci_root_intr(sc);
+               }
+       }
+}
+
+void
+avr32dci_interrupt(struct avr32dci_softc *sc)
+{
+       uint32_t status;
+
+       USB_BUS_LOCK(&sc->sc_bus);
+
+       /* read interrupt status */
+       status = AVR32_READ_4(sc, AVR32_INTSTA);
+
+       /* clear all set interrupts */
+       AVR32_WRITE_4(sc, AVR32_CLRINT, status);
+
+       DPRINTFN(14, "INTSTA=0x%08x\n", status);
+
+       /* check for any bus state change interrupts */
+       if (status & AVR32_INT_ENDRESET) {
+
+               DPRINTFN(5, "end of reset\n");
+
+               /* set correct state */
+               sc->sc_flags.status_bus_reset = 1;
+               sc->sc_flags.status_suspend = 0;
+               sc->sc_flags.change_suspend = 0;
+               sc->sc_flags.change_connect = 1;
+
+               /* disable resume interrupt */
+               avr32dci_mod_ien(sc, AVR32_INT_DET_SUSPD |
+                   AVR32_INT_ENDRESET, AVR32_INT_WAKE_UP);
+
+               /* complete root HUB interrupt endpoint */
+               avr32dci_root_intr(sc);
+       }
+       /*
+        * If resume and suspend is set at the same time we interpret
+        * that like RESUME. Resume is set when there is at least 3
+        * milliseconds of inactivity on the USB BUS.
+        */
+       if (status & AVR32_INT_WAKE_UP) {
+
+               DPRINTFN(5, "resume interrupt\n");
+
+               if (sc->sc_flags.status_suspend) {
+                       /* update status bits */
+                       sc->sc_flags.status_suspend = 0;
+                       sc->sc_flags.change_suspend = 1;
+
+                       /* disable resume interrupt */
+                       avr32dci_mod_ien(sc, AVR32_INT_DET_SUSPD |
+                           AVR32_INT_ENDRESET, AVR32_INT_WAKE_UP);
+
+                       /* complete root HUB interrupt endpoint */
+                       avr32dci_root_intr(sc);
+               }
+       } else if (status & AVR32_INT_DET_SUSPD) {
+
+               DPRINTFN(5, "suspend interrupt\n");
+
+               if (!sc->sc_flags.status_suspend) {
+                       /* update status bits */
+                       sc->sc_flags.status_suspend = 1;
+                       sc->sc_flags.change_suspend = 1;
+
+                       /* disable suspend interrupt */
+                       avr32dci_mod_ien(sc, AVR32_INT_WAKE_UP |
+                           AVR32_INT_ENDRESET, AVR32_INT_DET_SUSPD);
+
+                       /* complete root HUB interrupt endpoint */
+                       avr32dci_root_intr(sc);
+               }
+       }
+       /* check for any endpoint interrupts */
+       if (status & -AVR32_INT_EPT_INT(0)) {
+
+               DPRINTFN(5, "real endpoint interrupt\n");
+
+               avr32dci_interrupt_poll(sc);
+       }
+       USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+avr32dci_setup_standard_chain_sub(struct avr32dci_std_temp *temp)
+{
+       struct avr32dci_td *td;
+
+       /* get current Transfer Descriptor */
+       td = temp->td_next;
+       temp->td = td;
+
+       /* prepare for next TD */
+       temp->td_next = td->obj_next;
+
+       /* fill out the Transfer Descriptor */
+       td->func = temp->func;
+       td->pc = temp->pc;
+       td->offset = temp->offset;
+       td->remainder = temp->len;
+       td->error = 0;
+       td->did_stall = temp->did_stall;
+       td->short_pkt = temp->short_pkt;
+       td->alt_next = temp->setup_alt_next;
+}
+
+static void
+avr32dci_setup_standard_chain(struct usb_xfer *xfer)
+{
+       struct avr32dci_std_temp temp;
+       struct avr32dci_softc *sc;
+       struct avr32dci_td *td;
+       uint32_t x;
+       uint8_t ep_no;
+       uint8_t need_sync;
+
+       DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+           xfer->address, UE_GET_ADDR(xfer->endpointno),
+           xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
+
+       temp.max_frame_size = xfer->max_frame_size;
+
+       td = xfer->td_start[0];
+       xfer->td_transfer_first = td;
+       xfer->td_transfer_cache = td;
+
+       /* setup temp */
+
+       temp.pc = NULL;
+       temp.td = NULL;
+       temp.td_next = xfer->td_start[0];
+       temp.offset = 0;
+       temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+       temp.did_stall = !xfer->flags_int.control_stall;
+
+       sc = AVR32_BUS2SC(xfer->xroot->bus);
+       ep_no = (xfer->endpointno & UE_ADDR);
+
+       /* check if we should prepend a setup message */
+
+       if (xfer->flags_int.control_xfr) {
+               if (xfer->flags_int.control_hdr) {
+
+                       temp.func = &avr32dci_setup_rx;
+                       temp.len = xfer->frlengths[0];
+                       temp.pc = xfer->frbuffers + 0;
+                       temp.short_pkt = temp.len ? 1 : 0;
+                       /* check for last frame */
+                       if (xfer->nframes == 1) {
+                               /* no STATUS stage yet, SETUP is last */
+                               if (xfer->flags_int.control_act)
+                                       temp.setup_alt_next = 0;
+                       }
+                       avr32dci_setup_standard_chain_sub(&temp);
+               }
+               x = 1;
+       } else {
+               x = 0;
+       }
+
+       if (x != xfer->nframes) {
+               if (xfer->endpointno & UE_DIR_IN) {
+                       temp.func = &avr32dci_data_tx;
+                       need_sync = 1;
+               } else {
+                       temp.func = &avr32dci_data_rx;
+                       need_sync = 0;
+               }
+
+               /* setup "pc" pointer */
+               temp.pc = xfer->frbuffers + x;
+       } else {
+               need_sync = 0;
+       }
+       while (x != xfer->nframes) {
+
+               /* DATA0 / DATA1 message */
+
+               temp.len = xfer->frlengths[x];
+
+               x++;
+
+               if (x == xfer->nframes) {
+                       if (xfer->flags_int.control_xfr) {
+                               if (xfer->flags_int.control_act) {
+                                       temp.setup_alt_next = 0;
+                               }
+                       } else {
+                               temp.setup_alt_next = 0;
+                       }
+               }
+               if (temp.len == 0) {
+
+                       /* make sure that we send an USB packet */
+
+                       temp.short_pkt = 0;
+
+               } else {
+
+                       /* regular data transfer */
+
+                       temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+               }
+
+               avr32dci_setup_standard_chain_sub(&temp);
+
+               if (xfer->flags_int.isochronous_xfr) {
+                       temp.offset += temp.len;
+               } else {
+                       /* get next Page Cache pointer */
+                       temp.pc = xfer->frbuffers + x;
+               }
+       }
+
+       if (xfer->flags_int.control_xfr) {
+
+               /* always setup a valid "pc" pointer for status and sync */
+               temp.pc = xfer->frbuffers + 0;
+               temp.len = 0;
+               temp.short_pkt = 0;
+               temp.setup_alt_next = 0;
+
+               /* check if we need to sync */
+               if (need_sync) {
+                       /* we need a SYNC point after TX */
+                       temp.func = &avr32dci_data_tx_sync;
+                       avr32dci_setup_standard_chain_sub(&temp);
+               }
+               /* check if we should append a status stage */
+               if (!xfer->flags_int.control_act) {
+
+                       /*
+                        * Send a DATA1 message and invert the current
+                        * endpoint direction.
+                        */
+                       if (xfer->endpointno & UE_DIR_IN) {
+                               temp.func = &avr32dci_data_rx;
+                               need_sync = 0;
+                       } else {
+                               temp.func = &avr32dci_data_tx;
+                               need_sync = 1;
+                       }
+
+                       avr32dci_setup_standard_chain_sub(&temp);
+                       if (need_sync) {
+                               /* we need a SYNC point after TX */
+                               temp.func = &avr32dci_data_tx_sync;
+                               avr32dci_setup_standard_chain_sub(&temp);
+                       }
+               }
+       }
+       /* must have at least one frame! */
+       td = temp.td;
+       xfer->td_transfer_last = td;
+}
+
+static void
+avr32dci_timeout(void *arg)
+{
+       struct usb_xfer *xfer = arg;
+
+       DPRINTF("xfer=%p\n", xfer);
+
+       USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+       /* transfer is transferred */
+       avr32dci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+avr32dci_start_standard_chain(struct usb_xfer *xfer)
+{
+       DPRINTFN(9, "\n");
+
+       /* poll one time - will turn on interrupts */
+       if (avr32dci_xfer_do_fifo(xfer)) {
+               uint8_t ep_no = xfer->endpointno & UE_ADDR;
+               struct avr32dci_softc *sc = AVR32_BUS2SC(xfer->xroot->bus);
+
+               avr32dci_mod_ien(sc, AVR32_INT_EPT_INT(ep_no), 0);
+
+               /* put transfer on interrupt queue */
+               usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+               /* start timeout, if any */
+               if (xfer->timeout != 0) {
+                       usbd_transfer_timeout_ms(xfer,
+                           &avr32dci_timeout, xfer->timeout);
+               }
+       }
+}
+
+static void
+avr32dci_root_intr(struct avr32dci_softc *sc)
+{
+       DPRINTFN(9, "\n");
+
+       USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+       /* set port bit */
+       sc->sc_hub_idata[0] = 0x02;     /* we only have one port */
+
+       uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+           sizeof(sc->sc_hub_idata));
+}
+
+static usb_error_t
+avr32dci_standard_done_sub(struct usb_xfer *xfer)
+{<