aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/usb
diff options
context:
space:
mode:
authorAndrew Thompson <thompsa@FreeBSD.org>2009-02-23 18:31:00 +0000
committerAndrew Thompson <thompsa@FreeBSD.org>2009-02-23 18:31:00 +0000
commit02ac6454880b59bbc5f3f74dffaffa90b30adc8b (patch)
tree691e9b9009214e6138d3913e4c022c897e386667 /sys/dev/usb
parentab8b490a63599c4b0a045c989c1a7162b161d2a5 (diff)
downloadsrc-02ac6454880b59bbc5f3f74dffaffa90b30adc8b.tar.gz
src-02ac6454880b59bbc5f3f74dffaffa90b30adc8b.zip
Move the new USB stack into its new home.
Notes
Notes: svn path=/head/; revision=188942
Diffstat (limited to 'sys/dev/usb')
-rw-r--r--sys/dev/usb/README.TXT411
-rw-r--r--sys/dev/usb/bluetooth/TODO.TXT18
-rw-r--r--sys/dev/usb/bluetooth/ng_ubt.c1718
-rw-r--r--sys/dev/usb/bluetooth/ng_ubt_var.h131
-rw-r--r--sys/dev/usb/bluetooth/ubtbcmfw.c430
-rw-r--r--sys/dev/usb/controller/at91dci.c2467
-rw-r--r--sys/dev/usb/controller/at91dci.h245
-rw-r--r--sys/dev/usb/controller/at91dci_atmelarm.c347
-rw-r--r--sys/dev/usb/controller/atmegadci.c2327
-rw-r--r--sys/dev/usb/controller/atmegadci.h273
-rw-r--r--sys/dev/usb/controller/atmegadci_atmelarm.c27
-rw-r--r--sys/dev/usb/controller/ehci.c3965
-rw-r--r--sys/dev/usb/controller/ehci.h532
-rw-r--r--sys/dev/usb/controller/ehci_ixp4xx.c348
-rw-r--r--sys/dev/usb/controller/ehci_mbus.c364
-rw-r--r--sys/dev/usb/controller/ehci_pci.c486
-rw-r--r--sys/dev/usb/controller/musb_otg.c2875
-rw-r--r--sys/dev/usb/controller/musb_otg.h407
-rw-r--r--sys/dev/usb/controller/musb_otg_atmelarm.c239
-rw-r--r--sys/dev/usb/controller/ohci.c2862
-rw-r--r--sys/dev/usb/controller/ohci.h366
-rw-r--r--sys/dev/usb/controller/ohci_atmelarm.c223
-rw-r--r--sys/dev/usb/controller/ohci_pci.c387
-rw-r--r--sys/dev/usb/controller/uhci.c3381
-rw-r--r--sys/dev/usb/controller/uhci.h321
-rw-r--r--sys/dev/usb/controller/uhci_pci.c443
-rw-r--r--sys/dev/usb/controller/usb_controller.c620
-rw-r--r--sys/dev/usb/controller/uss820dci.c2489
-rw-r--r--sys/dev/usb/controller/uss820dci.h377
-rw-r--r--sys/dev/usb/controller/uss820dci_atmelarm.c238
-rw-r--r--sys/dev/usb/image/uscanner.c643
-rw-r--r--sys/dev/usb/input/uhid.c789
-rw-r--r--sys/dev/usb/input/ukbd.c1489
-rw-r--r--sys/dev/usb/input/ums.c901
-rw-r--r--sys/dev/usb/input/usb_rdesc.h276
-rw-r--r--sys/dev/usb/misc/udbp.c853
-rw-r--r--sys/dev/usb/misc/udbp.h80
-rw-r--r--sys/dev/usb/misc/ufm.c329
-rw-r--r--sys/dev/usb/net/if_aue.c1054
-rw-r--r--sys/dev/usb/net/if_auereg.h220
-rw-r--r--sys/dev/usb/net/if_axe.c1076
-rw-r--r--sys/dev/usb/net/if_axereg.h196
-rw-r--r--sys/dev/usb/net/if_cdce.c771
-rw-r--r--sys/dev/usb/net/if_cdcereg.h68
-rw-r--r--sys/dev/usb/net/if_cue.c645
-rw-r--r--sys/dev/usb/net/if_cuereg.h132
-rw-r--r--sys/dev/usb/net/if_kue.c704
-rw-r--r--sys/dev/usb/net/if_kuefw.h685
-rw-r--r--sys/dev/usb/net/if_kuereg.h141
-rw-r--r--sys/dev/usb/net/if_rue.c913
-rw-r--r--sys/dev/usb/net/if_ruereg.h183
-rw-r--r--sys/dev/usb/net/if_udav.c856
-rw-r--r--sys/dev/usb/net/if_udavreg.h166
-rw-r--r--sys/dev/usb/net/usb_ethernet.c587
-rw-r--r--sys/dev/usb/net/usb_ethernet.h122
-rw-r--r--sys/dev/usb/quirk/usb_quirk.c397
-rw-r--r--sys/dev/usb/quirk/usb_quirk.h59
-rw-r--r--sys/dev/usb/serial/u3g.c583
-rw-r--r--sys/dev/usb/serial/uark.c407
-rw-r--r--sys/dev/usb/serial/ubsa.c634
-rw-r--r--sys/dev/usb/serial/ubser.c518
-rw-r--r--sys/dev/usb/serial/uchcom.c883
-rw-r--r--sys/dev/usb/serial/ucycom.c564
-rw-r--r--sys/dev/usb/serial/ufoma.c1212
-rw-r--r--sys/dev/usb/serial/uftdi.c784
-rw-r--r--sys/dev/usb/serial/uftdi_reg.h340
-rw-r--r--sys/dev/usb/serial/ugensa.c352
-rw-r--r--sys/dev/usb/serial/uipaq.c1314
-rw-r--r--sys/dev/usb/serial/ulpt.c726
-rw-r--r--sys/dev/usb/serial/umct.c579
-rw-r--r--sys/dev/usb/serial/umodem.c788
-rw-r--r--sys/dev/usb/serial/umoscom.c672
-rw-r--r--sys/dev/usb/serial/uplcom.c831
-rw-r--r--sys/dev/usb/serial/usb_serial.c1127
-rw-r--r--sys/dev/usb/serial/usb_serial.h198
-rw-r--r--sys/dev/usb/serial/uslcom.c539
-rw-r--r--sys/dev/usb/serial/uvisor.c610
-rw-r--r--sys/dev/usb/serial/uvscom.c707
-rw-r--r--sys/dev/usb/sound/uaudio.c3750
-rw-r--r--sys/dev/usb/sound/uaudio.h63
-rw-r--r--sys/dev/usb/sound/uaudio_pcm.c234
-rw-r--r--sys/dev/usb/sound/uaudio_reg.h406
-rw-r--r--sys/dev/usb/storage/ata-usb.c1102
-rw-r--r--sys/dev/usb/storage/rio500_usb.h48
-rw-r--r--sys/dev/usb/storage/umass.c3619
-rw-r--r--sys/dev/usb/storage/urio.c479
-rw-r--r--sys/dev/usb/storage/ustorage_fs.c1897
-rw-r--r--sys/dev/usb/template/usb_template.c1312
-rw-r--r--sys/dev/usb/template/usb_template.h102
-rw-r--r--sys/dev/usb/template/usb_template_cdce.c292
-rw-r--r--sys/dev/usb/template/usb_template_msc.c199
-rw-r--r--sys/dev/usb/template/usb_template_mtp.c262
-rw-r--r--sys/dev/usb/ufm_ioctl.h39
-rw-r--r--sys/dev/usb/usb.h619
-rw-r--r--sys/dev/usb/usb_bus.h104
-rw-r--r--sys/dev/usb/usb_busdma.c1426
-rw-r--r--sys/dev/usb/usb_busdma.h183
-rw-r--r--sys/dev/usb/usb_cdc.h191
-rw-r--r--sys/dev/usb/usb_compat_linux.c1653
-rw-r--r--sys/dev/usb/usb_compat_linux.h472
-rw-r--r--sys/dev/usb/usb_controller.h199
-rw-r--r--sys/dev/usb/usb_core.c40
-rw-r--r--sys/dev/usb/usb_core.h467
-rw-r--r--sys/dev/usb/usb_debug.c152
-rw-r--r--sys/dev/usb/usb_debug.h70
-rw-r--r--sys/dev/usb/usb_defs.h77
-rw-r--r--sys/dev/usb/usb_dev.c2814
-rw-r--r--sys/dev/usb/usb_dev.h168
-rw-r--r--sys/dev/usb/usb_device.c2192
-rw-r--r--sys/dev/usb/usb_device.h187
-rw-r--r--sys/dev/usb/usb_dynamic.c155
-rw-r--r--sys/dev/usb/usb_dynamic.h70
-rw-r--r--sys/dev/usb/usb_endian.h119
-rw-r--r--sys/dev/usb/usb_error.c73
-rw-r--r--sys/dev/usb/usb_error.h63
-rw-r--r--sys/dev/usb/usb_generic.c2195
-rw-r--r--sys/dev/usb/usb_generic.h33
-rw-r--r--sys/dev/usb/usb_handle_request.c756
-rw-r--r--sys/dev/usb/usb_handle_request.h30
-rw-r--r--sys/dev/usb/usb_hid.c582
-rw-r--r--sys/dev/usb/usb_hid.h95
-rw-r--r--sys/dev/usb/usb_hub.c1842
-rw-r--r--sys/dev/usb/usb_hub.h80
-rw-r--r--sys/dev/usb/usb_if.m52
-rw-r--r--sys/dev/usb/usb_ioctl.h292
-rw-r--r--sys/dev/usb/usb_lookup.c134
-rw-r--r--sys/dev/usb/usb_lookup.h122
-rw-r--r--sys/dev/usb/usb_mbuf.c77
-rw-r--r--sys/dev/usb/usb_mbuf.h102
-rw-r--r--sys/dev/usb/usb_mfunc.h78
-rw-r--r--sys/dev/usb/usb_msctest.c578
-rw-r--r--sys/dev/usb/usb_msctest.h33
-rw-r--r--sys/dev/usb/usb_parse.c225
-rw-r--r--sys/dev/usb/usb_parse.h41
-rw-r--r--sys/dev/usb/usb_pci.h39
-rw-r--r--sys/dev/usb/usb_process.c426
-rw-r--r--sys/dev/usb/usb_process.h88
-rw-r--r--sys/dev/usb/usb_request.c1486
-rw-r--r--sys/dev/usb/usb_request.h103
-rw-r--r--sys/dev/usb/usb_revision.h65
-rw-r--r--sys/dev/usb/usb_sw_transfer.c170
-rw-r--r--sys/dev/usb/usb_sw_transfer.h62
-rw-r--r--sys/dev/usb/usb_transfer.c2826
-rw-r--r--sys/dev/usb/usb_transfer.h129
-rw-r--r--sys/dev/usb/usb_util.c346
-rw-r--r--sys/dev/usb/usb_util.h57
-rw-r--r--sys/dev/usb/usbdevs2527
-rw-r--r--sys/dev/usb/usbhid.h175
-rw-r--r--sys/dev/usb/wlan/if_rum.c2435
-rw-r--r--sys/dev/usb/wlan/if_rumfw.h213
-rw-r--r--sys/dev/usb/wlan/if_rumreg.h235
-rw-r--r--sys/dev/usb/wlan/if_rumvar.h156
-rw-r--r--sys/dev/usb/wlan/if_ural.c2364
-rw-r--r--sys/dev/usb/wlan/if_uralreg.h211
-rw-r--r--sys/dev/usb/wlan/if_uralvar.h155
-rw-r--r--sys/dev/usb/wlan/if_zyd.c3121
-rw-r--r--sys/dev/usb/wlan/if_zydfw.h1144
-rw-r--r--sys/dev/usb/wlan/if_zydreg.h1338
-rw-r--r--sys/dev/usb/wlan/usb_wlan.h57
159 files changed, 110593 insertions, 0 deletions
diff --git a/sys/dev/usb/README.TXT b/sys/dev/usb/README.TXT
new file mode 100644
index 000000000000..d24770c24068
--- /dev/null
+++ b/sys/dev/usb/README.TXT
@@ -0,0 +1,411 @@
+
+$FreeBSD$
+
+DESCRIPTION OF THE NEW USB API
+
+The new USB 2.0 API consists of 5 functions. All transfer types are
+managed using these functions. There is no longer need for separate
+functions to setup INTERRUPT- and ISOCHRONOUS- transfers.
+
++--------------------------------------------------------------+
+| |
+| "usb2_transfer_setup" - This function will allocate all |
+| necessary DMA memory and might |
+| sleep! |
+| |
+| "usb2_transfer_unsetup" - This function will stop the USB |
+| transfer, if it is currently |
+| active, release all DMA |
+| memory and might sleep! |
+| |
+| "usb2_transfer_start" - This function will start an USB |
+| transfer, if not already started.|
+| This function is always |
+| non-blocking. ** |
+| |
+| "usb2_transfer_stop" - This function will stop an USB |
+| transfer, if not already stopped.|
+| The callback function will be |
+| called before this function |
+| returns. This function is always |
+| non-blocking. ** |
+| |
+| "usb2_transfer_drain" - This function will stop an USB |
+| transfer, if not already stopped |
+| and wait for any additional |
+| DMA load operations to complete. |
+| Buffers that are loaded into DMA |
+| using "usb2_set_frame_data" can |
+| safely be freed after that |
+| this function has returned. This |
+| function can block the caller. |
+| |
+| ** These functions must be called with the private driver's |
+| lock locked. |
+| |
+| NOTE: These USB API functions are NULL safe, with regard |
+| to the USB transfer structure pointer. |
++--------------------------------------------------------------+
+
+Reference: /sys/dev/usb/usb_transfer.c
+
+/*
+ * A simple USB callback state-machine:
+ *
+ * +->-----------------------+
+ * | |
+ * +-<-+-------[tr_setup]--------+-<-+-<-[start/restart]
+ * | |
+ * | |
+ * | |
+ * +------>-[tr_transferred]---------+
+ * | |
+ * +--------->-[tr_error]------------+
+ */
+
+void
+usb2_default_callback(struct usb2_xfer *xfer)
+{
+ /*
+ * NOTE: it is not allowed to return
+ * before "USB_CHECK_STATUS()",
+ * even if the system is tearing down!
+ */
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ /*
+ * Setup xfer->frlengths[], xfer->nframes
+ * and write data to xfer->frbuffers[], if any
+ */
+
+ /**/
+ usb2_start_hardware(xfer);
+ return;
+
+ case USB_ST_TRANSFERRED:
+ /*
+ * Read data from xfer->frbuffers[], if any.
+ * "xfer->frlengths[]" should now have been
+ * updated to the actual length.
+ */
+ return;
+
+ default: /* Error */
+ /* print error message and clear stall for example */
+ return;
+ }
+}
+
+=== Notes for USB control transfers ===
+
+An USB control transfer has three parts. First the SETUP packet, then
+DATA packet(s) and then a STATUS packet. The SETUP packet is always
+pointed to by "xfer->frbuffers[0]" and the length is stored in
+"xfer->frlengths[0]" also if there should not be sent any SETUP
+packet! If an USB control transfer has no DATA stage, then
+"xfer->nframes" should be set to 1. Else the default value is
+"xfer->nframes" equal to 2.
+
+Example1: SETUP + STATUS
+ xfer->nframes = 1;
+ xfer->frlenghts[0] = 8;
+ usb2_start_hardware(xfer);
+
+Example2: SETUP + DATA + STATUS
+ xfer->nframes = 2;
+ xfer->frlenghts[0] = 8;
+ xfer->frlenghts[1] = 1;
+ usb2_start_hardware(xfer);
+
+Example3: SETUP + DATA + STATUS - split
+1st callback:
+ xfer->nframes = 1;
+ xfer->frlenghts[0] = 8;
+ usb2_start_hardware(xfer);
+
+2nd callback:
+ /* IMPORTANT: frbuffer[0] must still point at the setup packet! */
+ xfer->nframes = 2;
+ xfer->frlenghts[0] = 0;
+ xfer->frlenghts[1] = 1;
+ usb2_start_hardware(xfer);
+
+Example4: SETUP + STATUS - split
+1st callback:
+ xfer->nframes = 1;
+ xfer->frlenghts[0] = 8;
+ xfer->flags.manual_status = 1;
+ usb2_start_hardware(xfer);
+
+2nd callback:
+ xfer->nframes = 1;
+ xfer->frlenghts[0] = 0;
+ xfer->flags.manual_status = 0;
+ usb2_start_hardware(xfer);
+
+
+=== General USB transfer notes ===
+
+ 1) Something that one should be aware of is that all USB callbacks support
+recursation. That means one can start/stop whatever transfer from the callback
+of another transfer one desires. Also the transfer that is currently called
+back. Recursion is handled like this that when the callback that wants to
+recurse returns it is called one more time.
+
+ 2) After that the "usb2_start_hardware()" function has been called in
+the callback one can always depend on that "tr_error" or "tr_transferred"
+will get jumped afterwards. Always!
+
+ 3) Sleeping functions can only be called from the attach routine of the
+driver. Else one should not use sleeping functions unless one has to. It is
+very difficult with sleep, because one has to think that the device might have
+detached when the thread returns from sleep.
+
+ 4) Polling.
+
+ use_polling
+ This flag can be used with any callback and will cause the
+ "usb2_transfer_start()" function to wait using "DELAY()",
+ without exiting any mutexes, until the transfer is finished or
+ has timed out. This flag can be changed during operation.
+
+ NOTE: If polling is used the "timeout" field should be non-zero!
+ NOTE: USB_ERR_CANCELLED is returned in case of timeout
+ instead of USB_ERR_TIMEOUT!
+
+
+
+USB device driver examples:
+
+/sys/dev/usb/net/if_axe.c
+/sys/dev/usb/net/if_aue.c
+
+QUICK REFERENCE
+===============
+
+
+/*------------------------------------------------------------------------*
+ * usb2_error_t
+ * usb2_transfer_setup(udev, ifaces, pxfer, setup_start,
+ * n_setup, priv_sc, priv_mtx)
+ *------------------------------------------------------------------------*/
+
+- "udev" is a pointer to "struct usb2_device".
+
+- "ifaces" array of interface index numbers to use. See "if_index".
+
+- "pxfer" is a pointer to an array of USB transfer pointers that are
+ initialized to NULL, and then pointed to allocated USB transfers.
+
+- "setup_start" is a pointer to an array of USB config structures.
+
+- "n_setup" is a number telling the USB system how many USB transfers
+ should be setup.
+
+- "priv_sc" is the private softc pointer, which will be used to
+ initialize "xfer->priv_sc".
+
+- "priv_mtx" is the private mutex protecting the transfer structure and
+ the softc. This pointer is used to initialize "xfer->priv_mtx".
+
+/*------------------------------------------------------------------------*
+ * void
+ * usb2_transfer_unsetup(pxfer, n_setup)
+ *------------------------------------------------------------------------*/
+
+- "pxfer" is a pointer to an array of USB transfer pointers, that may
+ be NULL, that should be freed by the USB system.
+
+- "n_setup" is a number telling the USB system how many USB transfers
+ should be unsetup
+
+NOTE: This function can sleep, waiting for active mutexes to become unlocked!
+NOTE: It is not allowed to call "usb2_transfer_unsetup" from the callback
+ of a USB transfer.
+
+/*------------------------------------------------------------------------*
+ * void
+ * usb2_transfer_start(xfer)
+ *------------------------------------------------------------------------*/
+
+- "xfer" is pointer to a USB transfer that should be started
+
+NOTE: this function must be called with "priv_mtx" locked
+
+/*------------------------------------------------------------------------*
+ * void
+ * usb2_transfer_stop(xfer)
+ *------------------------------------------------------------------------*/
+
+- "xfer" is a pointer to a USB transfer that should be stopped
+
+NOTE: this function must be called with "priv_mtx" locked
+
+NOTE: if the transfer was in progress, the callback will called with
+ "xfer->error=USB_ERR_CANCELLED", before this function returns
+
+/*------------------------------------------------------------------------*
+ * struct usb2_config {
+ * type, endpoint, direction, interval, timeout, frames, index
+ * flags, bufsize, callback
+ * };
+ *------------------------------------------------------------------------*/
+
+- The "type" field selects the USB pipe type. Valid values are:
+ UE_INTERRUPT, UE_CONTROL, UE_BULK, UE_ISOCHRONOUS. The special
+ value UE_BULK_INTR will select BULK and INTERRUPT pipes.
+ This field is mandatory.
+
+- The "endpoint" field selects the USB endpoint number. A value of
+ 0xFF, "-1" or "UE_ADDR_ANY" will select the first matching endpoint.
+ This field is mandatory.
+
+- The "direction" field selects the USB endpoint direction. A value of
+ "UE_DIR_ANY" will select the first matching endpoint. Else valid
+ values are: "UE_DIR_IN" and "UE_DIR_OUT". "UE_DIR_IN" and
+ "UE_DIR_OUT" can be binary ORed by "UE_DIR_SID" which means that the
+ direction will be swapped in case of USB_MODE_DEVICE. Note that
+ "UE_DIR_IN" refers to the data transfer direction of the "IN" tokens
+ and "UE_DIR_OUT" refers to the data transfer direction of the "OUT"
+ tokens. This field is mandatory.
+
+- The "interval" field selects the interrupt interval. The value of this
+ field is given in milliseconds and is independent of device speed. Depending
+ on the endpoint type, this field has different meaning:
+
+ UE_INTERRUPT)
+ "0" use the default interrupt interval based on endpoint descriptor.
+ "Else" use the given value for polling rate.
+
+ UE_ISOCHRONOUS)
+ "0" use default.
+ "Else" the value is ignored.
+
+ UE_BULK)
+ UE_CONTROL)
+ "0" no transfer pre-delay.
+ "Else" a delay as given by this field in milliseconds is
+ inserted before the hardware is started when
+ "usb2_start_hardware()" is called.
+ NOTE: The transfer timeout, if any, is started after that
+ the pre-delay has elapsed!
+
+- The "timeout" field, if non-zero, will set the transfer timeout in
+ milliseconds. If the "timeout" field is zero and the transfer type
+ is ISOCHRONOUS a timeout of 250ms will be used.
+
+- The "frames" field sets the maximum number of frames. If zero is
+ specified it will yield the following results:
+
+ UE_BULK)
+ UE_INTERRUPT)
+ xfer->nframes = 1;
+
+ UE_CONTROL)
+ xfer->nframes = 2;
+
+ UE_ISOCHRONOUS)
+ Not allowed. Will cause an error.
+
+- The "ep_index" field allows you to give a number, in case more
+ endpoints match the description, that selects which matching
+ "ep_index" should be used.
+
+- The "if_index" field allows you to select which of the interface
+ numbers in the "ifaces" array parameter passed to "usb2_transfer_setup"
+ that should be used when setting up the given USB transfer.
+
+- The "flags" field has type "struct usb2_xfer_flags" and allows one
+ to set initial flags an USB transfer. Valid flags are:
+
+ force_short_xfer
+ This flag forces the last transmitted USB packet to be short.
+ A short packet has a length of less than "xfer->max_packet_size",
+ which derives from "wMaxPacketSize". This flag can be changed
+ during operation.
+
+ short_xfer_ok
+ This flag allows the received transfer length, "xfer->actlen"
+ to be less than "xfer->sumlen" upon completion of a transfer.
+ This flag can be changed during operation.
+
+ pipe_bof
+ This flag causes a failing USB transfer to remain first
+ in the PIPE queue except in the case of "xfer->error" equal
+ to "USB_ERR_CANCELLED". No other USB transfers in the affected
+ PIPE queue will be started until either:
+
+ 1) The failing USB transfer is stopped using "usb2_transfer_stop()".
+ 2) The failing USB transfer performs a successful transfer.
+
+ The purpose of this flag is to avoid races when multiple
+ transfers are queued for execution on an USB endpoint, and the
+ first executing transfer fails leading to the need for
+ clearing of stall for example. In this case this flag is used
+ to prevent the following USB transfers from being executed at
+ the same time the clear-stall command is executed on the USB
+ control endpoint. This flag can be changed during operation.
+
+ "BOF" is short for "Block On Failure"
+
+ NOTE: This flag should be set on all BULK and INTERRUPT
+ USB transfers which use an endpoint that can be shared
+ between userland and kernel.
+
+ proxy_buffer
+ Setting this flag will cause that the total buffer size will
+ be rounded up to the nearest atomic hardware transfer
+ size. The maximum data length of any USB transfer is always
+ stored in the "xfer->max_data_length". For control transfers
+ the USB kernel will allocate additional space for the 8-bytes
+ of SETUP header. These 8-bytes are not counted by the
+ "xfer->max_data_length" variable. This flag can not be changed
+ during operation.
+
+ ext_buffer
+ Setting this flag will cause that no data buffer will be
+ allocated. Instead the USB client must supply a data buffer.
+ This flag can not be changed during operation.
+
+ manual_status
+ Setting this flag prevents an USB STATUS stage to be appended
+ to the end of the USB control transfer. If no control data is
+ transferred this flag must be cleared. Else an error will be
+ returned to the USB callback. This flag is mostly useful for
+ the USB device side. This flag can be changed during
+ operation.
+
+ no_pipe_ok
+ Setting this flag causes the USB_ERR_NO_PIPE error to be
+ ignored. This flag can not be changed during operation.
+
+ stall_pipe
+ Setting this flag will cause STALL pids to be sent to the
+ endpoint belonging to this transfer before the transfer is
+ started. The transfer is started at the moment the host issues
+ a clear-stall command on the STALL'ed endpoint. This flag can
+ be changed during operation. This flag does only have effect
+ in USB device side mode except for control endpoints. This
+ flag is cleared when the stall command has been executed. This
+ flag can only be changed outside the callback function by
+ using the functions "usb2_transfer_set_stall()" and
+ "usb2_transfer_clear_stall()" !
+
+- The "bufsize" field sets the total buffer size in bytes. If
+ this field is zero, "wMaxPacketSize" will be used, multiplied by the
+ "frames" field if the transfer type is ISOCHRONOUS. This is useful for
+ setting up interrupt pipes. This field is mandatory.
+
+ NOTE: For control transfers "bufsize" includes
+ the length of the request structure.
+
+- The "callback" pointer sets the USB callback. This field is mandatory.
+
+MUTEX NOTE:
+===========
+
+When you create a mutex using "mtx_init()", don't forget to call
+"mtx_destroy()" at detach, else you can get "freed memory accessed"
+panics.
+
+--HPS
diff --git a/sys/dev/usb/bluetooth/TODO.TXT b/sys/dev/usb/bluetooth/TODO.TXT
new file mode 100644
index 000000000000..b0d6695ca9db
--- /dev/null
+++ b/sys/dev/usb/bluetooth/TODO.TXT
@@ -0,0 +1,18 @@
+$Id: TODO,v 1.1 2002/11/24 19:46:56 max Exp $
+$FreeBSD$
+
+1) SMP/Locking
+
+ The code makes use of ng_send_fn() whenever possible. Just
+ need to verify and make sure i did it right
+
+2) Firmware upgrade
+
+ According to Bluetooth spec device may present third interface
+ to perform firmware upgrade. 3Com USB Bluetooth dongle has
+ such interface. Need to implement set of Netgraph messages.
+
+3) Isochronous USB transfers (SCO data)
+
+ Tried to fix isochrounous transfers, which are still disabled
+ by default.
diff --git a/sys/dev/usb/bluetooth/ng_ubt.c b/sys/dev/usb/bluetooth/ng_ubt.c
new file mode 100644
index 000000000000..07ac2194699c
--- /dev/null
+++ b/sys/dev/usb/bluetooth/ng_ubt.c
@@ -0,0 +1,1718 @@
+/*
+ * ng_ubt.c
+ */
+
+/*-
+ * Copyright (c) 2001-2009 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: ng_ubt.c,v 1.16 2003/10/10 19:15:06 max Exp $
+ * $FreeBSD$
+ */
+
+/*
+ * NOTE: ng_ubt2 driver has a split personality. On one side it is
+ * a USB device driver and on the other it is a Netgraph node. This
+ * driver will *NOT* create traditional /dev/ enties, only Netgraph
+ * node.
+ *
+ * NOTE ON LOCKS USED: ng_ubt2 drives uses 2 locks (mutexes)
+ *
+ * 1) sc_if_mtx - lock for device's interface #0 and #1. This lock is used
+ * by USB for any USB request going over device's interface #0 and #1,
+ * i.e. interrupt, control, bulk and isoc. transfers.
+ *
+ * 2) sc_ng_mtx - this lock is used to protect shared (between USB, Netgraph
+ * and Taskqueue) data, such as outgoing mbuf queues, task flags and hook
+ * pointer. This lock *SHOULD NOT* be grabbed for a long time. In fact,
+ * think of it as a spin lock.
+ *
+ * NOTE ON LOCKING STRATEGY: ng_ubt2 driver operates in 3 different contexts.
+ *
+ * 1) USB context. This is where all the USB related stuff happens. All
+ * callbacks run in this context. All callbacks are called (by USB) with
+ * appropriate interface lock held. It is (generally) allowed to grab
+ * any additional locks.
+ *
+ * 2) Netgraph context. This is where all the Netgraph related stuff happens.
+ * Since we mark node as WRITER, the Netgraph node will be "locked" (from
+ * Netgraph point of view). Any variable that is only modified from the
+ * Netgraph context does not require any additonal locking. It is generally
+ * *NOT* allowed to grab *ANY* additional locks. Whatever you do, *DO NOT*
+ * grab any lock in the Netgraph context that could cause de-scheduling of
+ * the Netgraph thread for significant amount of time. In fact, the only
+ * lock that is allowed in the Netgraph context is the sc_ng_mtx lock.
+ * Also make sure that any code that is called from the Netgraph context
+ * follows the rule above.
+ *
+ * 3) Taskqueue context. This is where ubt_task runs. Since we are generally
+ * NOT allowed to grab any lock that could cause de-scheduling in the
+ * Netgraph context, and, USB requires us to grab interface lock before
+ * doing things with transfers, it is safer to transition from the Netgraph
+ * context to the Taskqueue context before we can call into USB subsystem.
+ *
+ * So, to put everything together, the rules are as follows.
+ * It is OK to call from the USB context or the Taskqueue context into
+ * the Netgraph context (i.e. call NG_SEND_xxx functions). In other words
+ * it is allowed to call into the Netgraph context with locks held.
+ * Is it *NOT* OK to call from the Netgraph context into the USB context,
+ * because USB requires us to grab interface locks, and, it is safer to
+ * avoid it. So, to make things safer we set task flags to indicate which
+ * actions we want to perform and schedule ubt_task which would run in the
+ * Taskqueue context.
+ * Is is OK to call from the Taskqueue context into the USB context,
+ * and, ubt_task does just that (i.e. grabs appropriate interface locks
+ * before calling into USB).
+ * Access to the outgoing queues, task flags and hook pointer is
+ * controlled by the sc_ng_mtx lock. It is an unavoidable evil. Again,
+ * sc_ng_mtx should really be a spin lock (and it is very likely to an
+ * equivalent of spin lock due to adaptive nature of freebsd mutexes).
+ * All USB callbacks accept softc pointer as a private data. USB ensures
+ * that this pointer is valid.
+ */
+
+#include "usbdevs.h"
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+
+#define USB_DEBUG_VAR usb2_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_parse.h>
+#include <dev/usb/usb_lookup.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+
+#include <sys/mbuf.h>
+#include <sys/taskqueue.h>
+
+#include <netgraph/ng_message.h>
+#include <netgraph/netgraph.h>
+#include <netgraph/ng_parse.h>
+#include <netgraph/bluetooth/include/ng_bluetooth.h>
+#include <netgraph/bluetooth/include/ng_hci.h>
+#include <netgraph/bluetooth/include/ng_ubt.h>
+
+#include <dev/usb/bluetooth/ng_ubt_var.h>
+
+static int ubt_modevent(module_t, int, void *);
+static device_probe_t ubt_probe;
+static device_attach_t ubt_attach;
+static device_detach_t ubt_detach;
+
+static void ubt_task_schedule(ubt_softc_p, int);
+static task_fn_t ubt_task;
+
+#define ubt_xfer_start(sc, i) usb2_transfer_start((sc)->sc_xfer[(i)])
+
+/* Netgraph methods */
+static ng_constructor_t ng_ubt_constructor;
+static ng_shutdown_t ng_ubt_shutdown;
+static ng_newhook_t ng_ubt_newhook;
+static ng_connect_t ng_ubt_connect;
+static ng_disconnect_t ng_ubt_disconnect;
+static ng_rcvmsg_t ng_ubt_rcvmsg;
+static ng_rcvdata_t ng_ubt_rcvdata;
+
+/* Queue length */
+static const struct ng_parse_struct_field ng_ubt_node_qlen_type_fields[] =
+{
+ { "queue", &ng_parse_int32_type, },
+ { "qlen", &ng_parse_int32_type, },
+ { NULL, }
+};
+static const struct ng_parse_type ng_ubt_node_qlen_type =
+{
+ &ng_parse_struct_type,
+ &ng_ubt_node_qlen_type_fields
+};
+
+/* Stat info */
+static const struct ng_parse_struct_field ng_ubt_node_stat_type_fields[] =
+{
+ { "pckts_recv", &ng_parse_uint32_type, },
+ { "bytes_recv", &ng_parse_uint32_type, },
+ { "pckts_sent", &ng_parse_uint32_type, },
+ { "bytes_sent", &ng_parse_uint32_type, },
+ { "oerrors", &ng_parse_uint32_type, },
+ { "ierrors", &ng_parse_uint32_type, },
+ { NULL, }
+};
+static const struct ng_parse_type ng_ubt_node_stat_type =
+{
+ &ng_parse_struct_type,
+ &ng_ubt_node_stat_type_fields
+};
+
+/* Netgraph node command list */
+static const struct ng_cmdlist ng_ubt_cmdlist[] =
+{
+ {
+ NGM_UBT_COOKIE,
+ NGM_UBT_NODE_SET_DEBUG,
+ "set_debug",
+ &ng_parse_uint16_type,
+ NULL
+ },
+ {
+ NGM_UBT_COOKIE,
+ NGM_UBT_NODE_GET_DEBUG,
+ "get_debug",
+ NULL,
+ &ng_parse_uint16_type
+ },
+ {
+ NGM_UBT_COOKIE,
+ NGM_UBT_NODE_SET_QLEN,
+ "set_qlen",
+ &ng_ubt_node_qlen_type,
+ NULL
+ },
+ {
+ NGM_UBT_COOKIE,
+ NGM_UBT_NODE_GET_QLEN,
+ "get_qlen",
+ &ng_ubt_node_qlen_type,
+ &ng_ubt_node_qlen_type
+ },
+ {
+ NGM_UBT_COOKIE,
+ NGM_UBT_NODE_GET_STAT,
+ "get_stat",
+ NULL,
+ &ng_ubt_node_stat_type
+ },
+ {
+ NGM_UBT_COOKIE,
+ NGM_UBT_NODE_RESET_STAT,
+ "reset_stat",
+ NULL,
+ NULL
+ },
+ { 0, }
+};
+
+/* Netgraph node type */
+static struct ng_type typestruct =
+{
+ .version = NG_ABI_VERSION,
+ .name = NG_UBT_NODE_TYPE,
+ .constructor = ng_ubt_constructor,
+ .rcvmsg = ng_ubt_rcvmsg,
+ .shutdown = ng_ubt_shutdown,
+ .newhook = ng_ubt_newhook,
+ .connect = ng_ubt_connect,
+ .rcvdata = ng_ubt_rcvdata,
+ .disconnect = ng_ubt_disconnect,
+ .cmdlist = ng_ubt_cmdlist
+};
+
+/****************************************************************************
+ ****************************************************************************
+ ** USB specific
+ ****************************************************************************
+ ****************************************************************************/
+
+/* USB methods */
+static usb2_callback_t ubt_ctrl_write_callback;
+static usb2_callback_t ubt_intr_read_callback;
+static usb2_callback_t ubt_bulk_read_callback;
+static usb2_callback_t ubt_bulk_write_callback;
+static usb2_callback_t ubt_isoc_read_callback;
+static usb2_callback_t ubt_isoc_write_callback;
+
+static int ubt_fwd_mbuf_up(ubt_softc_p, struct mbuf **);
+static int ubt_isoc_read_one_frame(struct usb2_xfer *, int);
+
+/*
+ * USB config
+ *
+ * The following desribes usb transfers that could be submitted on USB device.
+ *
+ * Interface 0 on the USB device must present the following endpoints
+ * 1) Interrupt endpoint to receive HCI events
+ * 2) Bulk IN endpoint to receive ACL data
+ * 3) Bulk OUT endpoint to send ACL data
+ *
+ * Interface 1 on the USB device must present the following endpoints
+ * 1) Isochronous IN endpoint to receive SCO data
+ * 2) Isochronous OUT endpoint to send SCO data
+ */
+
+static const struct usb2_config ubt_config[UBT_N_TRANSFER] =
+{
+ /*
+ * Interface #0
+ */
+
+ /* Outgoing bulk transfer - ACL packets */
+ [UBT_IF_0_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .if_index = 0,
+ .mh.bufsize = UBT_BULK_WRITE_BUFFER_SIZE,
+ .mh.flags = { .pipe_bof = 1, .force_short_xfer = 1, },
+ .mh.callback = &ubt_bulk_write_callback,
+ },
+ /* Incoming bulk transfer - ACL packets */
+ [UBT_IF_0_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .if_index = 0,
+ .mh.bufsize = UBT_BULK_READ_BUFFER_SIZE,
+ .mh.flags = { .pipe_bof = 1, .short_xfer_ok = 1, },
+ .mh.callback = &ubt_bulk_read_callback,
+ },
+ /* Incoming interrupt transfer - HCI events */
+ [UBT_IF_0_INTR_DT_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .if_index = 0,
+ .mh.flags = { .pipe_bof = 1, .short_xfer_ok = 1, },
+ .mh.bufsize = UBT_INTR_BUFFER_SIZE,
+ .mh.callback = &ubt_intr_read_callback,
+ },
+ /* Outgoing control transfer - HCI commands */
+ [UBT_IF_0_CTRL_DT_WR] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* control pipe */
+ .direction = UE_DIR_ANY,
+ .if_index = 0,
+ .mh.bufsize = UBT_CTRL_BUFFER_SIZE,
+ .mh.callback = &ubt_ctrl_write_callback,
+ .mh.timeout = 5000, /* 5 seconds */
+ },
+
+ /*
+ * Interface #1
+ */
+
+ /* Incoming isochronous transfer #1 - SCO packets */
+ [UBT_IF_1_ISOC_DT_RD1] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .if_index = 1,
+ .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */
+ .mh.frames = UBT_ISOC_NFRAMES,
+ .mh.flags = { .short_xfer_ok = 1, },
+ .mh.callback = &ubt_isoc_read_callback,
+ },
+ /* Incoming isochronous transfer #2 - SCO packets */
+ [UBT_IF_1_ISOC_DT_RD2] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .if_index = 1,
+ .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */
+ .mh.frames = UBT_ISOC_NFRAMES,
+ .mh.flags = { .short_xfer_ok = 1, },
+ .mh.callback = &ubt_isoc_read_callback,
+ },
+ /* Outgoing isochronous transfer #1 - SCO packets */
+ [UBT_IF_1_ISOC_DT_WR1] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .if_index = 1,
+ .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */
+ .mh.frames = UBT_ISOC_NFRAMES,
+ .mh.flags = { .short_xfer_ok = 1, },
+ .mh.callback = &ubt_isoc_write_callback,
+ },
+ /* Outgoing isochronous transfer #2 - SCO packets */
+ [UBT_IF_1_ISOC_DT_WR2] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .if_index = 1,
+ .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */
+ .mh.frames = UBT_ISOC_NFRAMES,
+ .mh.flags = { .short_xfer_ok = 1, },
+ .mh.callback = &ubt_isoc_write_callback,
+ },
+};
+
+/*
+ * If for some reason device should not be attached then put
+ * VendorID/ProductID pair into the list below. The format is
+ * as follows:
+ *
+ * { USB_VPI(VENDOR_ID, PRODUCT_ID, 0) },
+ *
+ * where VENDOR_ID and PRODUCT_ID are hex numbers.
+ */
+
+static const struct usb2_device_id ubt_ignore_devs[] =
+{
+ /* AVM USB Bluetooth-Adapter BlueFritz! v1.0 */
+ { USB_VPI(USB_VENDOR_AVM, 0x2200, 0) },
+};
+
+/* List of supported bluetooth devices */
+static const struct usb2_device_id ubt_devs[] =
+{
+ /* Generic Bluetooth class devices */
+ { USB_IFACE_CLASS(UDCLASS_WIRELESS),
+ USB_IFACE_SUBCLASS(UDSUBCLASS_RF),
+ USB_IFACE_PROTOCOL(UDPROTO_BLUETOOTH) },
+
+ /* AVM USB Bluetooth-Adapter BlueFritz! v2.0 */
+ { USB_VPI(USB_VENDOR_AVM, 0x3800, 0) },
+};
+
+/*
+ * Probe for a USB Bluetooth device.
+ * USB context.
+ */
+
+static int
+ubt_probe(device_t dev)
+{
+ struct usb2_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb2_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ if (uaa->info.bIfaceIndex != 0)
+ return (ENXIO);
+
+ if (uaa->use_generic == 0)
+ return (ENXIO);
+
+ if (usb2_lookup_id_by_uaa(ubt_ignore_devs,
+ sizeof(ubt_ignore_devs), uaa) == 0)
+ return (ENXIO);
+
+ return (usb2_lookup_id_by_uaa(ubt_devs, sizeof(ubt_devs), uaa));
+} /* ubt_probe */
+
+/*
+ * Attach the device.
+ * USB context.
+ */
+
+static int
+ubt_attach(device_t dev)
+{
+ struct usb2_attach_arg *uaa = device_get_ivars(dev);
+ struct ubt_softc *sc = device_get_softc(dev);
+ struct usb2_endpoint_descriptor *ed;
+ uint16_t wMaxPacketSize;
+ uint8_t alt_index, i, j;
+ uint8_t iface_index[2] = { 0, 1 };
+
+ device_set_usb2_desc(dev);
+
+ sc->sc_dev = dev;
+ sc->sc_debug = NG_UBT_WARN_LEVEL;
+
+ /*
+ * Create Netgraph node
+ */
+
+ if (ng_make_node_common(&typestruct, &sc->sc_node) != 0) {
+ UBT_ALERT(sc, "could not create Netgraph node\n");
+ return (ENXIO);
+ }
+
+ /* Name Netgraph node */
+ if (ng_name_node(sc->sc_node, device_get_nameunit(dev)) != 0) {
+ UBT_ALERT(sc, "could not name Netgraph node\n");
+ NG_NODE_UNREF(sc->sc_node);
+ return (ENXIO);
+ }
+ NG_NODE_SET_PRIVATE(sc->sc_node, sc);
+ NG_NODE_FORCE_WRITER(sc->sc_node);
+
+ /*
+ * Initialize device softc structure
+ */
+
+ /* initialize locks */
+ mtx_init(&sc->sc_ng_mtx, "ubt ng", NULL, MTX_DEF);
+ mtx_init(&sc->sc_if_mtx, "ubt if", NULL, MTX_DEF | MTX_RECURSE);
+
+ /* initialize packet queues */
+ NG_BT_MBUFQ_INIT(&sc->sc_cmdq, UBT_DEFAULT_QLEN);
+ NG_BT_MBUFQ_INIT(&sc->sc_aclq, UBT_DEFAULT_QLEN);
+ NG_BT_MBUFQ_INIT(&sc->sc_scoq, UBT_DEFAULT_QLEN);
+
+ /* initialize glue task */
+ TASK_INIT(&sc->sc_task, 0, ubt_task, sc);
+
+ /*
+ * Configure Bluetooth USB device. Discover all required USB
+ * interfaces and endpoints.
+ *
+ * USB device must present two interfaces:
+ * 1) Interface 0 that has 3 endpoints
+ * 1) Interrupt endpoint to receive HCI events
+ * 2) Bulk IN endpoint to receive ACL data
+ * 3) Bulk OUT endpoint to send ACL data
+ *
+ * 2) Interface 1 then has 2 endpoints
+ * 1) Isochronous IN endpoint to receive SCO data
+ * 2) Isochronous OUT endpoint to send SCO data
+ *
+ * Interface 1 (with isochronous endpoints) has several alternate
+ * configurations with different packet size.
+ */
+
+ /*
+ * For interface #1 search alternate settings, and find
+ * the descriptor with the largest wMaxPacketSize
+ */
+
+ wMaxPacketSize = 0;
+ alt_index = 0;
+ i = 0;
+ j = 0;
+
+ /* Search through all the descriptors looking for bidir mode */
+ while (1) {
+ uint16_t temp;
+
+ ed = usb2_find_edesc(usb2_get_config_descriptor(uaa->device),
+ 1, i, j);
+ if (ed == NULL) {
+ if (j != 0) {
+ /* next interface */
+ j = 0;
+ i ++;
+ continue;
+ }
+
+ break; /* end of interfaces */
+ }
+
+ temp = UGETW(ed->wMaxPacketSize);
+ if (temp > wMaxPacketSize) {
+ wMaxPacketSize = temp;
+ alt_index = i;
+ }
+
+ j ++;
+ }
+
+ /* Set alt configuration on interface #1 only if we found it */
+ if (wMaxPacketSize > 0 &&
+ usb2_set_alt_interface_index(uaa->device, 1, alt_index)) {
+ UBT_ALERT(sc, "could not set alternate setting %d " \
+ "for interface 1!\n", alt_index);
+ goto detach;
+ }
+
+ /* Setup transfers for both interfaces */
+ if (usb2_transfer_setup(uaa->device, iface_index, sc->sc_xfer,
+ ubt_config, UBT_N_TRANSFER, sc, &sc->sc_if_mtx)) {
+ UBT_ALERT(sc, "could not allocate transfers\n");
+ goto detach;
+ }
+
+ /* Claim all interfaces on the device */
+ for (i = 1; usb2_get_iface(uaa->device, i) != NULL; i ++)
+ usb2_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex);
+
+ return (0); /* success */
+
+detach:
+ ubt_detach(dev);
+
+ return (ENXIO);
+} /* ubt_attach */
+
+/*
+ * Detach the device.
+ * USB context.
+ */
+
+int
+ubt_detach(device_t dev)
+{
+ struct ubt_softc *sc = device_get_softc(dev);
+ node_p node = sc->sc_node;
+
+ /* Destroy Netgraph node */
+ if (node != NULL) {
+ sc->sc_node = NULL;
+ NG_NODE_REALLY_DIE(node);
+ ng_rmnode_self(node);
+ }
+
+ /* Make sure ubt_task in gone */
+ taskqueue_drain(taskqueue_swi, &sc->sc_task);
+
+ /* Free USB transfers, if any */
+ usb2_transfer_unsetup(sc->sc_xfer, UBT_N_TRANSFER);
+
+ /* Destroy queues */
+ UBT_NG_LOCK(sc);
+ NG_BT_MBUFQ_DESTROY(&sc->sc_cmdq);
+ NG_BT_MBUFQ_DESTROY(&sc->sc_aclq);
+ NG_BT_MBUFQ_DESTROY(&sc->sc_scoq);
+ UBT_NG_UNLOCK(sc);
+
+ mtx_destroy(&sc->sc_if_mtx);
+ mtx_destroy(&sc->sc_ng_mtx);
+
+ return (0);
+} /* ubt_detach */
+
+/*
+ * Called when outgoing control request (HCI command) has completed, i.e.
+ * HCI command was sent to the device.
+ * USB context.
+ */
+
+static void
+ubt_ctrl_write_callback(struct usb2_xfer *xfer)
+{
+ struct ubt_softc *sc = xfer->priv_sc;
+ struct usb2_device_request req;
+ struct mbuf *m;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ UBT_INFO(sc, "sent %d bytes to control pipe\n", xfer->actlen);
+ UBT_STAT_BYTES_SENT(sc, xfer->actlen);
+ UBT_STAT_PCKTS_SENT(sc);
+ /* FALLTHROUGH */
+
+ case USB_ST_SETUP:
+send_next:
+ /* Get next command mbuf, if any */
+ UBT_NG_LOCK(sc);
+ NG_BT_MBUFQ_DEQUEUE(&sc->sc_cmdq, m);
+ UBT_NG_UNLOCK(sc);
+
+ if (m == NULL) {
+ UBT_INFO(sc, "HCI command queue is empty\n");
+ break; /* transfer complete */
+ }
+
+ /* Initialize a USB control request and then schedule it */
+ bzero(&req, sizeof(req));
+ req.bmRequestType = UBT_HCI_REQUEST;
+ USETW(req.wLength, m->m_pkthdr.len);
+
+ UBT_INFO(sc, "Sending control request, " \
+ "bmRequestType=0x%02x, wLength=%d\n",
+ req.bmRequestType, UGETW(req.wLength));
+
+ usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req));
+ usb2_m_copy_in(xfer->frbuffers + 1, 0, m, 0, m->m_pkthdr.len);
+
+ xfer->frlengths[0] = sizeof(req);
+ xfer->frlengths[1] = m->m_pkthdr.len;
+ xfer->nframes = 2;
+
+ NG_FREE_M(m);
+
+ usb2_start_hardware(xfer);
+ break;
+
+ default: /* Error */
+ if (xfer->error != USB_ERR_CANCELLED) {
+ UBT_WARN(sc, "control transfer failed: %s\n",
+ usb2_errstr(xfer->error));
+
+ UBT_STAT_OERROR(sc);
+ goto send_next;
+ }
+
+ /* transfer cancelled */
+ break;
+ }
+} /* ubt_ctrl_write_callback */
+
+/*
+ * Called when incoming interrupt transfer (HCI event) has completed, i.e.
+ * HCI event was received from the device.
+ * USB context.
+ */
+
+static void
+ubt_intr_read_callback(struct usb2_xfer *xfer)
+{
+ struct ubt_softc *sc = xfer->priv_sc;
+ struct mbuf *m;
+ ng_hci_event_pkt_t *hdr;
+
+ m = NULL;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ /* Allocate a new mbuf */
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+ UBT_STAT_IERROR(sc);
+ goto submit_next;
+ }
+
+ MCLGET(m, M_DONTWAIT);
+ if (!(m->m_flags & M_EXT)) {
+ UBT_STAT_IERROR(sc);
+ goto submit_next;
+ }
+
+ /* Add HCI packet type */
+ *mtod(m, uint8_t *)= NG_HCI_EVENT_PKT;
+ m->m_pkthdr.len = m->m_len = 1;
+
+ if (xfer->actlen > MCLBYTES - 1)
+ xfer->actlen = MCLBYTES - 1;
+
+ usb2_copy_out(xfer->frbuffers, 0, mtod(m, uint8_t *) + 1,
+ xfer->actlen);
+ m->m_pkthdr.len += xfer->actlen;
+ m->m_len += xfer->actlen;
+
+ UBT_INFO(sc, "got %d bytes from interrupt pipe\n",
+ xfer->actlen);
+
+ /* Validate packet and send it up the stack */
+ if (m->m_pkthdr.len < sizeof(*hdr)) {
+ UBT_INFO(sc, "HCI event packet is too short\n");
+
+ UBT_STAT_IERROR(sc);
+ goto submit_next;
+ }
+
+ hdr = mtod(m, ng_hci_event_pkt_t *);
+ if (hdr->length != (m->m_pkthdr.len - sizeof(*hdr))) {
+ UBT_ERR(sc, "Invalid HCI event packet size, " \
+ "length=%d, pktlen=%d\n",
+ hdr->length, m->m_pkthdr.len);
+
+ UBT_STAT_IERROR(sc);
+ goto submit_next;
+ }
+
+ UBT_INFO(sc, "got complete HCI event frame, pktlen=%d, " \
+ "length=%d\n", m->m_pkthdr.len, hdr->length);
+
+ UBT_STAT_PCKTS_RECV(sc);
+ UBT_STAT_BYTES_RECV(sc, m->m_pkthdr.len);
+
+ ubt_fwd_mbuf_up(sc, &m);
+ /* m == NULL at this point */
+ /* FALLTHROUGH */
+
+ case USB_ST_SETUP:
+submit_next:
+ NG_FREE_M(m); /* checks for m != NULL */
+
+ xfer->frlengths[0] = xfer->max_data_length;
+ usb2_start_hardware(xfer);
+ break;
+
+ default: /* Error */
+ if (xfer->error != USB_ERR_CANCELLED) {
+ UBT_WARN(sc, "interrupt transfer failed: %s\n",
+ usb2_errstr(xfer->error));
+
+ /* Try to clear stall first */
+ xfer->flags.stall_pipe = 1;
+ goto submit_next;
+ }
+ /* transfer cancelled */
+ break;
+ }
+} /* ubt_intr_read_callback */
+
+/*
+ * Called when incoming bulk transfer (ACL packet) has completed, i.e.
+ * ACL packet was received from the device.
+ * USB context.
+ */
+
+static void
+ubt_bulk_read_callback(struct usb2_xfer *xfer)
+{
+ struct ubt_softc *sc = xfer->priv_sc;
+ struct mbuf *m;
+ ng_hci_acldata_pkt_t *hdr;
+ uint16_t len;
+
+ m = NULL;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ /* Allocate new mbuf */
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+ UBT_STAT_IERROR(sc);
+ goto submit_next;
+ }
+
+ MCLGET(m, M_DONTWAIT);
+ if (!(m->m_flags & M_EXT)) {
+ UBT_STAT_IERROR(sc);
+ goto submit_next;
+ }
+
+ /* Add HCI packet type */
+ *mtod(m, uint8_t *)= NG_HCI_ACL_DATA_PKT;
+ m->m_pkthdr.len = m->m_len = 1;
+
+ if (xfer->actlen > MCLBYTES - 1)
+ xfer->actlen = MCLBYTES - 1;
+
+ usb2_copy_out(xfer->frbuffers, 0, mtod(m, uint8_t *) + 1,
+ xfer->actlen);
+ m->m_pkthdr.len += xfer->actlen;
+ m->m_len += xfer->actlen;
+
+ UBT_INFO(sc, "got %d bytes from bulk-in pipe\n",
+ xfer->actlen);
+
+ /* Validate packet and send it up the stack */
+ if (m->m_pkthdr.len < sizeof(*hdr)) {
+ UBT_INFO(sc, "HCI ACL packet is too short\n");
+
+ UBT_STAT_IERROR(sc);
+ goto submit_next;
+ }
+
+ hdr = mtod(m, ng_hci_acldata_pkt_t *);
+ len = le16toh(hdr->length);
+ if (len != (m->m_pkthdr.len - sizeof(*hdr))) {
+ UBT_ERR(sc, "Invalid ACL packet size, length=%d, " \
+ "pktlen=%d\n", len, m->m_pkthdr.len);
+
+ UBT_STAT_IERROR(sc);
+ goto submit_next;
+ }
+
+ UBT_INFO(sc, "got complete ACL data packet, pktlen=%d, " \
+ "length=%d\n", m->m_pkthdr.len, len);
+
+ UBT_STAT_PCKTS_RECV(sc);
+ UBT_STAT_BYTES_RECV(sc, m->m_pkthdr.len);
+
+ ubt_fwd_mbuf_up(sc, &m);
+ /* m == NULL at this point */
+ /* FALLTHOUGH */
+
+ case USB_ST_SETUP:
+submit_next:
+ NG_FREE_M(m); /* checks for m != NULL */
+
+ xfer->frlengths[0] = xfer->max_data_length;
+ usb2_start_hardware(xfer);
+ break;
+
+ default: /* Error */
+ if (xfer->error != USB_ERR_CANCELLED) {
+ UBT_WARN(sc, "bulk-in transfer failed: %s\n",
+ usb2_errstr(xfer->error));
+
+ /* Try to clear stall first */
+ xfer->flags.stall_pipe = 1;
+ goto submit_next;
+ }
+ /* transfer cancelled */
+ break;
+ }
+} /* ubt_bulk_read_callback */
+
+/*
+ * Called when outgoing bulk transfer (ACL packet) has completed, i.e.
+ * ACL packet was sent to the device.
+ * USB context.
+ */
+
+static void
+ubt_bulk_write_callback(struct usb2_xfer *xfer)
+{
+ struct ubt_softc *sc = xfer->priv_sc;
+ struct mbuf *m;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ UBT_INFO(sc, "sent %d bytes to bulk-out pipe\n", xfer->actlen);
+ UBT_STAT_BYTES_SENT(sc, xfer->actlen);
+ UBT_STAT_PCKTS_SENT(sc);
+ /* FALLTHROUGH */
+
+ case USB_ST_SETUP:
+send_next:
+ /* Get next mbuf, if any */
+ UBT_NG_LOCK(sc);
+ NG_BT_MBUFQ_DEQUEUE(&sc->sc_aclq, m);
+ UBT_NG_UNLOCK(sc);
+
+ if (m == NULL) {
+ UBT_INFO(sc, "ACL data queue is empty\n");
+ break; /* transfer completed */
+ }
+
+ /*
+ * Copy ACL data frame back to a linear USB transfer buffer
+ * and schedule transfer
+ */
+
+ usb2_m_copy_in(xfer->frbuffers, 0, m, 0, m->m_pkthdr.len);
+ xfer->frlengths[0] = m->m_pkthdr.len;
+
+ UBT_INFO(sc, "bulk-out transfer has been started, len=%d\n",
+ m->m_pkthdr.len);
+
+ NG_FREE_M(m);
+
+ usb2_start_hardware(xfer);
+ break;
+
+ default: /* Error */
+ if (xfer->error != USB_ERR_CANCELLED) {
+ UBT_WARN(sc, "bulk-out transfer failed: %s\n",
+ usb2_errstr(xfer->error));
+
+ UBT_STAT_OERROR(sc);
+
+ /* try to clear stall first */
+ xfer->flags.stall_pipe = 1;
+ goto send_next;
+ }
+ /* transfer cancelled */
+ break;
+ }
+} /* ubt_bulk_write_callback */
+
+/*
+ * Called when incoming isoc transfer (SCO packet) has completed, i.e.
+ * SCO packet was received from the device.
+ * USB context.
+ */
+
+static void
+ubt_isoc_read_callback(struct usb2_xfer *xfer)
+{
+ struct ubt_softc *sc = xfer->priv_sc;
+ int n;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ for (n = 0; n < xfer->nframes; n ++)
+ if (ubt_isoc_read_one_frame(xfer, n) < 0)
+ break;
+ /* FALLTHROUGH */
+
+ case USB_ST_SETUP:
+read_next:
+ for (n = 0; n < xfer->nframes; n ++)
+ xfer->frlengths[n] = xfer->max_frame_size;
+
+ usb2_start_hardware(xfer);
+ break;
+
+ default: /* Error */
+ if (xfer->error != USB_ERR_CANCELLED) {
+ UBT_STAT_IERROR(sc);
+ goto read_next;
+ }
+
+ /* transfer cancelled */
+ break;
+ }
+} /* ubt_isoc_read_callback */
+
+/*
+ * Helper function. Called from ubt_isoc_read_callback() to read
+ * SCO data from one frame.
+ * USB context.
+ */
+
+static int
+ubt_isoc_read_one_frame(struct usb2_xfer *xfer, int frame_no)
+{
+ struct ubt_softc *sc = xfer->priv_sc;
+ struct mbuf *m;
+ int len, want, got;
+
+ /* Get existing SCO reassembly buffer */
+ m = sc->sc_isoc_in_buffer;
+ sc->sc_isoc_in_buffer = NULL;
+
+ /* While we have data in the frame */
+ while ((len = xfer->frlengths[frame_no]) > 0) {
+ if (m == NULL) {
+ /* Start new reassembly buffer */
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+ UBT_STAT_IERROR(sc);
+ return (-1); /* XXX out of sync! */
+ }
+
+ MCLGET(m, M_DONTWAIT);
+ if (!(m->m_flags & M_EXT)) {
+ UBT_STAT_IERROR(sc);
+ NG_FREE_M(m);
+ return (-1); /* XXX out of sync! */
+ }
+
+ /* Expect SCO header */
+ *mtod(m, uint8_t *) = NG_HCI_SCO_DATA_PKT;
+ m->m_pkthdr.len = m->m_len = got = 1;
+ want = sizeof(ng_hci_scodata_pkt_t);
+ } else {
+ /*
+ * Check if we have SCO header and if so
+ * adjust amount of data we want
+ */
+ got = m->m_pkthdr.len;
+ want = sizeof(ng_hci_scodata_pkt_t);
+
+ if (got >= want)
+ want += mtod(m, ng_hci_scodata_pkt_t *)->length;
+ }
+
+ /* Append frame data to the SCO reassembly buffer */
+ if (got + len > want)
+ len = want - got;
+
+ usb2_copy_out(xfer->frbuffers, frame_no * xfer->max_frame_size,
+ mtod(m, uint8_t *) + m->m_pkthdr.len, len);
+
+ m->m_pkthdr.len += len;
+ m->m_len += len;
+ xfer->frlengths[frame_no] -= len;
+
+ /* Check if we got everything we wanted, if not - continue */
+ if (got != want)
+ continue;
+
+ /* If we got here then we got complete SCO frame */
+ UBT_INFO(sc, "got complete SCO data frame, pktlen=%d, " \
+ "length=%d\n", m->m_pkthdr.len,
+ mtod(m, ng_hci_scodata_pkt_t *)->length);
+
+ UBT_STAT_PCKTS_RECV(sc);
+ UBT_STAT_BYTES_RECV(sc, m->m_pkthdr.len);
+
+ ubt_fwd_mbuf_up(sc, &m);
+ /* m == NULL at this point */
+ }
+
+ /* Put SCO reassembly buffer back */
+ sc->sc_isoc_in_buffer = m;
+
+ return (0);
+} /* ubt_isoc_read_one_frame */
+
+/*
+ * Called when outgoing isoc transfer (SCO packet) has completed, i.e.
+ * SCO packet was sent to the device.
+ * USB context.
+ */
+
+static void
+ubt_isoc_write_callback(struct usb2_xfer *xfer)
+{
+ struct ubt_softc *sc = xfer->priv_sc;
+ struct mbuf *m;
+ int n, space, offset;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ UBT_INFO(sc, "sent %d bytes to isoc-out pipe\n", xfer->actlen);
+ UBT_STAT_BYTES_SENT(sc, xfer->actlen);
+ UBT_STAT_PCKTS_SENT(sc);
+ /* FALLTHROUGH */
+
+ case USB_ST_SETUP:
+send_next:
+ offset = 0;
+ space = xfer->max_frame_size * xfer->nframes;
+ m = NULL;
+
+ while (space > 0) {
+ if (m == NULL) {
+ UBT_NG_LOCK(sc);
+ NG_BT_MBUFQ_DEQUEUE(&sc->sc_scoq, m);
+ UBT_NG_UNLOCK(sc);
+
+ if (m == NULL)
+ break;
+ }
+
+ n = min(space, m->m_pkthdr.len);
+ if (n > 0) {
+ usb2_m_copy_in(xfer->frbuffers, offset, m,0, n);
+ m_adj(m, n);
+
+ offset += n;
+ space -= n;
+ }
+
+ if (m->m_pkthdr.len == 0)
+ NG_FREE_M(m); /* sets m = NULL */
+ }
+
+ /* Put whatever is left from mbuf back on queue */
+ if (m != NULL) {
+ UBT_NG_LOCK(sc);
+ NG_BT_MBUFQ_PREPEND(&sc->sc_scoq, m);
+ UBT_NG_UNLOCK(sc);
+ }
+
+ /*
+ * Calculate sizes for isoc frames.
+ * Note that offset could be 0 at this point (i.e. we have
+ * nothing to send). That is fine, as we have isoc. transfers
+ * going in both directions all the time. In this case it
+ * would be just empty isoc. transfer.
+ */
+
+ for (n = 0; n < xfer->nframes; n ++) {
+ xfer->frlengths[n] = min(offset, xfer->max_frame_size);
+ offset -= xfer->frlengths[n];
+ }
+
+ usb2_start_hardware(xfer);
+ break;
+
+ default: /* Error */
+ if (xfer->error != USB_ERR_CANCELLED) {
+ UBT_STAT_OERROR(sc);
+ goto send_next;
+ }
+
+ /* transfer cancelled */
+ break;
+ }
+}
+
+/*
+ * Utility function to forward provided mbuf upstream (i.e. up the stack).
+ * Modifies value of the mbuf pointer (sets it to NULL).
+ * Save to call from any context.
+ */
+
+static int
+ubt_fwd_mbuf_up(ubt_softc_p sc, struct mbuf **m)
+{
+ hook_p hook;
+ int error;
+
+ /*
+ * Close the race with Netgraph hook newhook/disconnect methods.
+ * Save the hook pointer atomically. Two cases are possible:
+ *
+ * 1) The hook pointer is NULL. It means disconnect method got
+ * there first. In this case we are done.
+ *
+ * 2) The hook pointer is not NULL. It means that hook pointer
+ * could be either in valid or invalid (i.e. in the process
+ * of disconnect) state. In any case grab an extra reference
+ * to protect the hook pointer.
+ *
+ * It is ok to pass hook in invalid state to NG_SEND_DATA_ONLY() as
+ * it checks for it. Drop extra reference after NG_SEND_DATA_ONLY().
+ */
+
+ UBT_NG_LOCK(sc);
+ if ((hook = sc->sc_hook) != NULL)
+ NG_HOOK_REF(hook);
+ UBT_NG_UNLOCK(sc);
+
+ if (hook == NULL) {
+ NG_FREE_M(*m);
+ return (ENETDOWN);
+ }
+
+ NG_SEND_DATA_ONLY(error, hook, *m);
+ NG_HOOK_UNREF(hook);
+
+ if (error != 0)
+ UBT_STAT_IERROR(sc);
+
+ return (error);
+} /* ubt_fwd_mbuf_up */
+
+/****************************************************************************
+ ****************************************************************************
+ ** Glue
+ ****************************************************************************
+ ****************************************************************************/
+
+/*
+ * Schedule glue task. Should be called with sc_ng_mtx held.
+ * Netgraph context.
+ */
+
+static void
+ubt_task_schedule(ubt_softc_p sc, int action)
+{
+ mtx_assert(&sc->sc_ng_mtx, MA_OWNED);
+
+ /*
+ * Try to handle corner case when "start all" and "stop all"
+ * actions can both be set before task is executed.
+ *
+ * The rules are
+ *
+ * sc_task_flags action new sc_task_flags
+ * ------------------------------------------------------
+ * 0 start start
+ * 0 stop stop
+ * start start start
+ * start stop stop
+ * stop start stop|start
+ * stop stop stop
+ * stop|start start stop|start
+ * stop|start stop stop
+ */
+
+ if (action != 0) {
+ if ((action & UBT_FLAG_T_STOP_ALL) != 0)
+ sc->sc_task_flags &= ~UBT_FLAG_T_START_ALL;
+
+ sc->sc_task_flags |= action;
+ }
+
+ if (sc->sc_task_flags & UBT_FLAG_T_PENDING)
+ return;
+
+ if (taskqueue_enqueue(taskqueue_swi, &sc->sc_task) == 0) {
+ sc->sc_task_flags |= UBT_FLAG_T_PENDING;
+ return;
+ }
+
+ /* XXX: i think this should never happen */
+} /* ubt_task_schedule */
+
+/*
+ * Glue task. Examines sc_task_flags and does things depending on it.
+ * Taskqueue context.
+ */
+
+static void
+ubt_task(void *context, int pending)
+{
+ ubt_softc_p sc = context;
+ int task_flags, i;
+
+ UBT_NG_LOCK(sc);
+ task_flags = sc->sc_task_flags;
+ sc->sc_task_flags = 0;
+ UBT_NG_UNLOCK(sc);
+
+ /*
+ * Stop all USB transfers synchronously.
+ * Stop interface #0 and #1 transfers at the same time and in the
+ * same loop. usb2_transfer_drain() will do appropriate locking.
+ */
+
+ if (task_flags & UBT_FLAG_T_STOP_ALL)
+ for (i = 0; i < UBT_N_TRANSFER; i ++)
+ usb2_transfer_drain(sc->sc_xfer[i]);
+
+ /* Start incoming interrupt and bulk, and all isoc. USB transfers */
+ if (task_flags & UBT_FLAG_T_START_ALL) {
+ /*
+ * Interface #0
+ */
+
+ mtx_lock(&sc->sc_if_mtx);
+
+ ubt_xfer_start(sc, UBT_IF_0_INTR_DT_RD);
+ ubt_xfer_start(sc, UBT_IF_0_BULK_DT_RD);
+
+ /*
+ * Interface #1
+ * Start both read and write isoc. transfers by default.
+ * Get them going all the time even if we have nothing
+ * to send to avoid any delays.
+ */
+
+ ubt_xfer_start(sc, UBT_IF_1_ISOC_DT_RD1);
+ ubt_xfer_start(sc, UBT_IF_1_ISOC_DT_RD2);
+ ubt_xfer_start(sc, UBT_IF_1_ISOC_DT_WR1);
+ ubt_xfer_start(sc, UBT_IF_1_ISOC_DT_WR2);
+
+ mtx_unlock(&sc->sc_if_mtx);
+ }
+
+ /* Start outgoing control transfer */
+ if (task_flags & UBT_FLAG_T_START_CTRL) {
+ mtx_lock(&sc->sc_if_mtx);
+ ubt_xfer_start(sc, UBT_IF_0_CTRL_DT_WR);
+ mtx_unlock(&sc->sc_if_mtx);
+ }
+
+ /* Start outgoing bulk transfer */
+ if (task_flags & UBT_FLAG_T_START_BULK) {
+ mtx_lock(&sc->sc_if_mtx);
+ ubt_xfer_start(sc, UBT_IF_0_BULK_DT_WR);
+ mtx_unlock(&sc->sc_if_mtx);
+ }
+} /* ubt_task */
+
+/****************************************************************************
+ ****************************************************************************
+ ** Netgraph specific
+ ****************************************************************************
+ ****************************************************************************/
+
+/*
+ * Netgraph node constructor. Do not allow to create node of this type.
+ * Netgraph context.
+ */
+
+static int
+ng_ubt_constructor(node_p node)
+{
+ return (EINVAL);
+} /* ng_ubt_constructor */
+
+/*
+ * Netgraph node destructor. Destroy node only when device has been detached.
+ * Netgraph context.
+ */
+
+static int
+ng_ubt_shutdown(node_p node)
+{
+ if (node->nd_flags & NGF_REALLY_DIE) {
+ /*
+ * We came here because the USB device is being
+ * detached, so stop being persistant.
+ */
+ NG_NODE_SET_PRIVATE(node, NULL);
+ NG_NODE_UNREF(node);
+ } else
+ NG_NODE_REVIVE(node); /* tell ng_rmnode we are persisant */
+
+ return (0);
+} /* ng_ubt_shutdown */
+
+/*
+ * Create new hook. There can only be one.
+ * Netgraph context.
+ */
+
+static int
+ng_ubt_newhook(node_p node, hook_p hook, char const *name)
+{
+ struct ubt_softc *sc = NG_NODE_PRIVATE(node);
+
+ if (strcmp(name, NG_UBT_HOOK) != 0)
+ return (EINVAL);
+
+ UBT_NG_LOCK(sc);
+ if (sc->sc_hook != NULL) {
+ UBT_NG_UNLOCK(sc);
+
+ return (EISCONN);
+ }
+
+ sc->sc_hook = hook;
+ UBT_NG_UNLOCK(sc);
+
+ return (0);
+} /* ng_ubt_newhook */
+
+/*
+ * Connect hook. Start incoming USB transfers.
+ * Netgraph context.
+ */
+
+static int
+ng_ubt_connect(hook_p hook)
+{
+ struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
+
+ NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook));
+
+ UBT_NG_LOCK(sc);
+ ubt_task_schedule(sc, UBT_FLAG_T_START_ALL);
+ UBT_NG_UNLOCK(sc);
+
+ return (0);
+} /* ng_ubt_connect */
+
+/*
+ * Disconnect hook.
+ * Netgraph context.
+ */
+
+static int
+ng_ubt_disconnect(hook_p hook)
+{
+ struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
+
+ UBT_NG_LOCK(sc);
+
+ if (hook != sc->sc_hook) {
+ UBT_NG_UNLOCK(sc);
+
+ return (EINVAL);
+ }
+
+ sc->sc_hook = NULL;
+
+ /* Kick off task to stop all USB xfers */
+ ubt_task_schedule(sc, UBT_FLAG_T_STOP_ALL);
+
+ /* Drain queues */
+ NG_BT_MBUFQ_DRAIN(&sc->sc_cmdq);
+ NG_BT_MBUFQ_DRAIN(&sc->sc_aclq);
+ NG_BT_MBUFQ_DRAIN(&sc->sc_scoq);
+
+ UBT_NG_UNLOCK(sc);
+
+ return (0);
+} /* ng_ubt_disconnect */
+
+/*
+ * Process control message.
+ * Netgraph context.
+ */
+
+static int
+ng_ubt_rcvmsg(node_p node, item_p item, hook_p lasthook)
+{
+ struct ubt_softc *sc = NG_NODE_PRIVATE(node);
+ struct ng_mesg *msg, *rsp = NULL;
+ struct ng_bt_mbufq *q;
+ int error = 0, queue, qlen;
+
+ NGI_GET_MSG(item, msg);
+
+ switch (msg->header.typecookie) {
+ case NGM_GENERIC_COOKIE:
+ switch (msg->header.cmd) {
+ case NGM_TEXT_STATUS:
+ NG_MKRESPONSE(rsp, msg, NG_TEXTRESPONSE, M_NOWAIT);
+ if (rsp == NULL) {
+ error = ENOMEM;
+ break;
+ }
+
+ snprintf(rsp->data, NG_TEXTRESPONSE,
+ "Hook: %s\n" \
+ "Task flags: %#x\n" \
+ "Debug: %d\n" \
+ "CMD queue: [have:%d,max:%d]\n" \
+ "ACL queue: [have:%d,max:%d]\n" \
+ "SCO queue: [have:%d,max:%d]",
+ (sc->sc_hook != NULL) ? NG_UBT_HOOK : "",
+ sc->sc_task_flags,
+ sc->sc_debug,
+ sc->sc_cmdq.len,
+ sc->sc_cmdq.maxlen,
+ sc->sc_aclq.len,
+ sc->sc_aclq.maxlen,
+ sc->sc_scoq.len,
+ sc->sc_scoq.maxlen);
+ break;
+
+ default:
+ error = EINVAL;
+ break;
+ }
+ break;
+
+ case NGM_UBT_COOKIE:
+ switch (msg->header.cmd) {
+ case NGM_UBT_NODE_SET_DEBUG:
+ if (msg->header.arglen != sizeof(ng_ubt_node_debug_ep)){
+ error = EMSGSIZE;
+ break;
+ }
+
+ sc->sc_debug = *((ng_ubt_node_debug_ep *) (msg->data));
+ break;
+
+ case NGM_UBT_NODE_GET_DEBUG:
+ NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_debug_ep),
+ M_NOWAIT);
+ if (rsp == NULL) {
+ error = ENOMEM;
+ break;
+ }
+
+ *((ng_ubt_node_debug_ep *) (rsp->data)) = sc->sc_debug;
+ break;
+
+ case NGM_UBT_NODE_SET_QLEN:
+ if (msg->header.arglen != sizeof(ng_ubt_node_qlen_ep)) {
+ error = EMSGSIZE;
+ break;
+ }
+
+ queue = ((ng_ubt_node_qlen_ep *) (msg->data))->queue;
+ qlen = ((ng_ubt_node_qlen_ep *) (msg->data))->qlen;
+
+ switch (queue) {
+ case NGM_UBT_NODE_QUEUE_CMD:
+ q = &sc->sc_cmdq;
+ break;
+
+ case NGM_UBT_NODE_QUEUE_ACL:
+ q = &sc->sc_aclq;
+ break;
+
+ case NGM_UBT_NODE_QUEUE_SCO:
+ q = &sc->sc_scoq;
+ break;
+
+ default:
+ error = EINVAL;
+ goto done;
+ /* NOT REACHED */
+ }
+
+ q->maxlen = qlen;
+ break;
+
+ case NGM_UBT_NODE_GET_QLEN:
+ if (msg->header.arglen != sizeof(ng_ubt_node_qlen_ep)) {
+ error = EMSGSIZE;
+ break;
+ }
+
+ queue = ((ng_ubt_node_qlen_ep *) (msg->data))->queue;
+
+ switch (queue) {
+ case NGM_UBT_NODE_QUEUE_CMD:
+ q = &sc->sc_cmdq;
+ break;
+
+ case NGM_UBT_NODE_QUEUE_ACL:
+ q = &sc->sc_aclq;
+ break;
+
+ case NGM_UBT_NODE_QUEUE_SCO:
+ q = &sc->sc_scoq;
+ break;
+
+ default:
+ error = EINVAL;
+ goto done;
+ /* NOT REACHED */
+ }
+
+ NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_qlen_ep),
+ M_NOWAIT);
+ if (rsp == NULL) {
+ error = ENOMEM;
+ break;
+ }
+
+ ((ng_ubt_node_qlen_ep *) (rsp->data))->queue = queue;
+ ((ng_ubt_node_qlen_ep *) (rsp->data))->qlen = q->maxlen;
+ break;
+
+ case NGM_UBT_NODE_GET_STAT:
+ NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_stat_ep),
+ M_NOWAIT);
+ if (rsp == NULL) {
+ error = ENOMEM;
+ break;
+ }
+
+ bcopy(&sc->sc_stat, rsp->data,
+ sizeof(ng_ubt_node_stat_ep));
+ break;
+
+ case NGM_UBT_NODE_RESET_STAT:
+ UBT_STAT_RESET(sc);
+ break;
+
+ default:
+ error = EINVAL;
+ break;
+ }
+ break;
+
+ default:
+ error = EINVAL;
+ break;
+ }
+done:
+ NG_RESPOND_MSG(error, node, item, rsp);
+ NG_FREE_MSG(msg);
+
+ return (error);
+} /* ng_ubt_rcvmsg */
+
+/*
+ * Process data.
+ * Netgraph context.
+ */
+
+static int
+ng_ubt_rcvdata(hook_p hook, item_p item)
+{
+ struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
+ struct mbuf *m;
+ struct ng_bt_mbufq *q;
+ int action, error = 0;
+
+ if (hook != sc->sc_hook) {
+ error = EINVAL;
+ goto done;
+ }
+
+ /* Deatch mbuf and get HCI frame type */
+ NGI_GET_M(item, m);
+
+ /*
+ * Minimal size of the HCI frame is 4 bytes: 1 byte frame type,
+ * 2 bytes connection handle and at least 1 byte of length.
+ * Panic on data frame that has size smaller than 4 bytes (it
+ * should not happen)
+ */
+
+ if (m->m_pkthdr.len < 4)
+ panic("HCI frame size is too small! pktlen=%d\n",
+ m->m_pkthdr.len);
+
+ /* Process HCI frame */
+ switch (*mtod(m, uint8_t *)) { /* XXX call m_pullup ? */
+ case NG_HCI_CMD_PKT:
+ if (m->m_pkthdr.len - 1 > UBT_CTRL_BUFFER_SIZE)
+ panic("HCI command frame size is too big! " \
+ "buffer size=%zd, packet len=%d\n",
+ UBT_CTRL_BUFFER_SIZE, m->m_pkthdr.len);
+
+ q = &sc->sc_cmdq;
+ action = UBT_FLAG_T_START_CTRL;
+ break;
+
+ case NG_HCI_ACL_DATA_PKT:
+ if (m->m_pkthdr.len - 1 > UBT_BULK_WRITE_BUFFER_SIZE)
+ panic("ACL data frame size is too big! " \
+ "buffer size=%d, packet len=%d\n",
+ UBT_BULK_WRITE_BUFFER_SIZE, m->m_pkthdr.len);
+
+ q = &sc->sc_aclq;
+ action = UBT_FLAG_T_START_BULK;
+ break;
+
+ case NG_HCI_SCO_DATA_PKT:
+ q = &sc->sc_scoq;
+ action = 0;
+ break;
+
+ default:
+ UBT_ERR(sc, "Dropping unsupported HCI frame, type=0x%02x, " \
+ "pktlen=%d\n", *mtod(m, uint8_t *), m->m_pkthdr.len);
+
+ NG_FREE_M(m);
+ error = EINVAL;
+ goto done;
+ /* NOT REACHED */
+ }
+
+ UBT_NG_LOCK(sc);
+ if (NG_BT_MBUFQ_FULL(q)) {
+ NG_BT_MBUFQ_DROP(q);
+ UBT_NG_UNLOCK(sc);
+
+ UBT_ERR(sc, "Dropping HCI frame 0x%02x, len=%d. Queue full\n",
+ *mtod(m, uint8_t *), m->m_pkthdr.len);
+
+ NG_FREE_M(m);
+ } else {
+ /* Loose HCI packet type, enqueue mbuf and kick off task */
+ m_adj(m, sizeof(uint8_t));
+ NG_BT_MBUFQ_ENQUEUE(q, m);
+ ubt_task_schedule(sc, action);
+ UBT_NG_UNLOCK(sc);
+ }
+done:
+ NG_FREE_ITEM(item);
+
+ return (error);
+} /* ng_ubt_rcvdata */
+
+/****************************************************************************
+ ****************************************************************************
+ ** Module
+ ****************************************************************************
+ ****************************************************************************/
+
+/*
+ * Load/Unload the driver module
+ */
+
+static int
+ubt_modevent(module_t mod, int event, void *data)
+{
+ int error;
+
+ switch (event) {
+ case MOD_LOAD:
+ error = ng_newtype(&typestruct);
+ if (error != 0)
+ printf("%s: Could not register Netgraph node type, " \
+ "error=%d\n", NG_UBT_NODE_TYPE, error);
+ break;
+
+ case MOD_UNLOAD:
+ error = ng_rmtype(&typestruct);
+ break;
+
+ default:
+ error = EOPNOTSUPP;
+ break;
+ }
+
+ return (error);
+} /* ubt_modevent */
+
+static devclass_t ubt_devclass;
+
+static device_method_t ubt_methods[] =
+{
+ DEVMETHOD(device_probe, ubt_probe),
+ DEVMETHOD(device_attach, ubt_attach),
+ DEVMETHOD(device_detach, ubt_detach),
+ { 0, 0 }
+};
+
+static driver_t ubt_driver =
+{
+ .name = "ubt",
+ .methods = ubt_methods,
+ .size = sizeof(struct ubt_softc),
+};
+
+DRIVER_MODULE(ng_ubt, ushub, ubt_driver, ubt_devclass, ubt_modevent, 0);
+MODULE_VERSION(ng_ubt, NG_BLUETOOTH_VERSION);
+MODULE_DEPEND(ng_ubt, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION);
+MODULE_DEPEND(ng_ubt, ng_hci, NG_BLUETOOTH_VERSION, NG_BLUETOOTH_VERSION, NG_BLUETOOTH_VERSION);
+MODULE_DEPEND(ng_ubt, usb, 1, 1, 1);
+
diff --git a/sys/dev/usb/bluetooth/ng_ubt_var.h b/sys/dev/usb/bluetooth/ng_ubt_var.h
new file mode 100644
index 000000000000..721e2f15b4ee
--- /dev/null
+++ b/sys/dev/usb/bluetooth/ng_ubt_var.h
@@ -0,0 +1,131 @@
+/*
+ * ng_ubt_var.h
+ */
+
+/*-
+ * Copyright (c) 2001-2009 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: ng_ubt_var.h,v 1.2 2003/03/22 23:44:36 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _NG_UBT_VAR_H_
+#define _NG_UBT_VAR_H_ 1
+
+/* Debug printf's */
+#define UBT_DEBUG(level, sc, fmt, ...) \
+do { \
+ if ((sc)->sc_debug >= (level)) \
+ device_printf((sc)->sc_dev, "%s:%d: " fmt, \
+ __FUNCTION__, __LINE__,## __VA_ARGS__); \
+} while (0)
+
+#define UBT_ALERT(...) UBT_DEBUG(NG_UBT_ALERT_LEVEL, __VA_ARGS__)
+#define UBT_ERR(...) UBT_DEBUG(NG_UBT_ERR_LEVEL, __VA_ARGS__)
+#define UBT_WARN(...) UBT_DEBUG(NG_UBT_WARN_LEVEL, __VA_ARGS__)
+#define UBT_INFO(...) UBT_DEBUG(NG_UBT_INFO_LEVEL, __VA_ARGS__)
+
+#define UBT_NG_LOCK(sc) mtx_lock(&(sc)->sc_ng_mtx)
+#define UBT_NG_UNLOCK(sc) mtx_unlock(&(sc)->sc_ng_mtx)
+
+/* Bluetooth USB control request type */
+#define UBT_HCI_REQUEST 0x20
+#define UBT_DEFAULT_QLEN 64
+#define UBT_ISOC_NFRAMES 32 /* should be factor of 8 */
+
+/* Bluetooth USB defines */
+enum {
+ /* Interface #0 transfers */
+ UBT_IF_0_BULK_DT_WR = 0,
+ UBT_IF_0_BULK_DT_RD,
+ UBT_IF_0_INTR_DT_RD,
+ UBT_IF_0_CTRL_DT_WR,
+
+ /* Interface #1 transfers */
+ UBT_IF_1_ISOC_DT_RD1,
+ UBT_IF_1_ISOC_DT_RD2,
+ UBT_IF_1_ISOC_DT_WR1,
+ UBT_IF_1_ISOC_DT_WR2,
+
+ UBT_N_TRANSFER, /* total number of transfers */
+};
+
+/* USB device softc structure */
+struct ubt_softc {
+ device_t sc_dev; /* for debug printf */
+
+ /* State */
+ ng_ubt_node_debug_ep sc_debug; /* debug level */
+
+ ng_ubt_node_stat_ep sc_stat; /* statistic */
+#define UBT_STAT_PCKTS_SENT(sc) (sc)->sc_stat.pckts_sent ++
+#define UBT_STAT_BYTES_SENT(sc, n) (sc)->sc_stat.bytes_sent += (n)
+#define UBT_STAT_PCKTS_RECV(sc) (sc)->sc_stat.pckts_recv ++
+#define UBT_STAT_BYTES_RECV(sc, n) (sc)->sc_stat.bytes_recv += (n)
+#define UBT_STAT_OERROR(sc) (sc)->sc_stat.oerrors ++
+#define UBT_STAT_IERROR(sc) (sc)->sc_stat.ierrors ++
+#define UBT_STAT_RESET(sc) bzero(&(sc)->sc_stat, sizeof((sc)->sc_stat))
+
+ /* USB device specific */
+ struct mtx sc_if_mtx; /* interfaces lock */
+ struct usb2_xfer *sc_xfer[UBT_N_TRANSFER];
+
+ struct mtx sc_ng_mtx; /* lock for shared NG data */
+
+ /* HCI commands */
+ struct ng_bt_mbufq sc_cmdq; /* HCI command queue */
+#define UBT_CTRL_BUFFER_SIZE (sizeof(struct usb2_device_request) + \
+ sizeof(ng_hci_cmd_pkt_t) + NG_HCI_CMD_PKT_SIZE)
+#define UBT_INTR_BUFFER_SIZE (MCLBYTES-1) /* reserve 1 byte for ID-tag */
+
+ /* ACL data */
+ struct ng_bt_mbufq sc_aclq; /* ACL data queue */
+#define UBT_BULK_READ_BUFFER_SIZE (MCLBYTES-1) /* reserve 1 byte for ID-tag */
+#define UBT_BULK_WRITE_BUFFER_SIZE (MCLBYTES)
+
+ /* SCO data */
+ struct ng_bt_mbufq sc_scoq; /* SCO data queue */
+ struct mbuf *sc_isoc_in_buffer; /* SCO reassembly buffer */
+
+ /* Netgraph specific */
+ node_p sc_node; /* pointer back to node */
+ hook_p sc_hook; /* upstream hook */
+
+ /* Glue */
+ int sc_task_flags; /* task flags */
+#define UBT_FLAG_T_PENDING (1 << 0) /* task pending */
+#define UBT_FLAG_T_STOP_ALL (1 << 1) /* stop all xfers */
+#define UBT_FLAG_T_START_ALL (1 << 2) /* start all read and isoc
+ write xfers */
+#define UBT_FLAG_T_START_CTRL (1 << 3) /* start control xfer (write) */
+#define UBT_FLAG_T_START_BULK (1 << 4) /* start bulk xfer (write) */
+
+ struct task sc_task;
+};
+typedef struct ubt_softc ubt_softc_t;
+typedef struct ubt_softc * ubt_softc_p;
+
+#endif /* ndef _NG_UBT_VAR_H_ */
+
diff --git a/sys/dev/usb/bluetooth/ubtbcmfw.c b/sys/dev/usb/bluetooth/ubtbcmfw.c
new file mode 100644
index 000000000000..3685f5671736
--- /dev/null
+++ b/sys/dev/usb/bluetooth/ubtbcmfw.c
@@ -0,0 +1,430 @@
+/*
+ * ubtbcmfw.c
+ */
+
+/*-
+ * Copyright (c) 2003-2009 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: ubtbcmfw.c,v 1.3 2003/10/10 19:15:08 max Exp $
+ * $FreeBSD$
+ */
+
+#include "usbdevs.h"
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_ioctl.h>
+
+#define USB_DEBUG_VAR usb2_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_parse.h>
+#include <dev/usb/usb_lookup.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_mbuf.h>
+#include <dev/usb/usb_dev.h>
+
+/*
+ * Download firmware to BCM2033.
+ */
+
+#define UBTBCMFW_CONFIG_NO 1 /* Config number */
+#define UBTBCMFW_IFACE_IDX 0 /* Control interface */
+
+#define UBTBCMFW_BSIZE 1024
+#define UBTBCMFW_IFQ_MAXLEN 2
+
+enum {
+ UBTBCMFW_BULK_DT_WR = 0,
+ UBTBCMFW_INTR_DT_RD,
+ UBTBCMFW_N_TRANSFER,
+};
+
+struct ubtbcmfw_softc {
+ struct usb2_device *sc_udev;
+ struct mtx sc_mtx;
+ struct usb2_xfer *sc_xfer[UBTBCMFW_N_TRANSFER];
+ struct usb2_fifo_sc sc_fifo;
+};
+
+/*
+ * Prototypes
+ */
+
+static device_probe_t ubtbcmfw_probe;
+static device_attach_t ubtbcmfw_attach;
+static device_detach_t ubtbcmfw_detach;
+
+static usb2_callback_t ubtbcmfw_write_callback;
+static usb2_callback_t ubtbcmfw_read_callback;
+
+static usb2_fifo_close_t ubtbcmfw_close;
+static usb2_fifo_cmd_t ubtbcmfw_start_read;
+static usb2_fifo_cmd_t ubtbcmfw_start_write;
+static usb2_fifo_cmd_t ubtbcmfw_stop_read;
+static usb2_fifo_cmd_t ubtbcmfw_stop_write;
+static usb2_fifo_ioctl_t ubtbcmfw_ioctl;
+static usb2_fifo_open_t ubtbcmfw_open;
+
+static struct usb2_fifo_methods ubtbcmfw_fifo_methods =
+{
+ .f_close = &ubtbcmfw_close,
+ .f_ioctl = &ubtbcmfw_ioctl,
+ .f_open = &ubtbcmfw_open,
+ .f_start_read = &ubtbcmfw_start_read,
+ .f_start_write = &ubtbcmfw_start_write,
+ .f_stop_read = &ubtbcmfw_stop_read,
+ .f_stop_write = &ubtbcmfw_stop_write,
+ .basename[0] = "ubtbcmfw",
+ .basename[1] = "ubtbcmfw",
+ .basename[2] = "ubtbcmfw",
+ .postfix[0] = "",
+ .postfix[1] = ".1",
+ .postfix[2] = ".2",
+};
+
+/*
+ * Device's config structure
+ */
+
+static const struct usb2_config ubtbcmfw_config[UBTBCMFW_N_TRANSFER] =
+{
+ [UBTBCMFW_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = 0x02, /* fixed */
+ .direction = UE_DIR_OUT,
+ .if_index = UBTBCMFW_IFACE_IDX,
+ .mh.bufsize = UBTBCMFW_BSIZE,
+ .mh.flags = { .pipe_bof = 1, .force_short_xfer = 1,
+ .proxy_buffer = 1, },
+ .mh.callback = &ubtbcmfw_write_callback,
+ },
+
+ [UBTBCMFW_INTR_DT_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = 0x01, /* fixed */
+ .direction = UE_DIR_IN,
+ .if_index = UBTBCMFW_IFACE_IDX,
+ .mh.bufsize = UBTBCMFW_BSIZE,
+ .mh.flags = { .pipe_bof = 1, .short_xfer_ok = 1,
+ .proxy_buffer = 1, },
+ .mh.callback = &ubtbcmfw_read_callback,
+ },
+};
+
+/*
+ * Module
+ */
+
+static devclass_t ubtbcmfw_devclass;
+
+static device_method_t ubtbcmfw_methods[] =
+{
+ DEVMETHOD(device_probe, ubtbcmfw_probe),
+ DEVMETHOD(device_attach, ubtbcmfw_attach),
+ DEVMETHOD(device_detach, ubtbcmfw_detach),
+ {0, 0}
+};
+
+static driver_t ubtbcmfw_driver =
+{
+ .name = "ubtbcmfw",
+ .methods = ubtbcmfw_methods,
+ .size = sizeof(struct ubtbcmfw_softc),
+};
+
+DRIVER_MODULE(ubtbcmfw, ushub, ubtbcmfw_driver, ubtbcmfw_devclass, NULL, 0);
+MODULE_DEPEND(ubtbcmfw, usb, 1, 1, 1);
+
+/*
+ * Probe for a USB Bluetooth device
+ */
+
+static int
+ubtbcmfw_probe(device_t dev)
+{
+ const struct usb2_device_id devs[] = {
+ /* Broadcom BCM2033 devices only */
+ { USB_VPI(USB_VENDOR_BROADCOM, USB_PRODUCT_BROADCOM_BCM2033, 0) },
+ };
+
+ struct usb2_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb2_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ if (uaa->info.bIfaceIndex != 0)
+ return (ENXIO);
+
+ return (usb2_lookup_id_by_uaa(devs, sizeof(devs), uaa));
+} /* ubtbcmfw_probe */
+
+/*
+ * Attach the device
+ */
+
+static int
+ubtbcmfw_attach(device_t dev)
+{
+ struct usb2_attach_arg *uaa = device_get_ivars(dev);
+ struct ubtbcmfw_softc *sc = device_get_softc(dev);
+ uint8_t iface_index;
+ int error;
+
+ sc->sc_udev = uaa->device;
+
+ device_set_usb2_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "ubtbcmfw lock", NULL, MTX_DEF | MTX_RECURSE);
+
+ iface_index = UBTBCMFW_IFACE_IDX;
+ error = usb2_transfer_setup(uaa->device, &iface_index, sc->sc_xfer,
+ ubtbcmfw_config, UBTBCMFW_N_TRANSFER,
+ sc, &sc->sc_mtx);
+ if (error != 0) {
+ device_printf(dev, "allocating USB transfers failed. %s\n",
+ usb2_errstr(error));
+ goto detach;
+ }
+
+ /* Set interface permissions */
+ usb2_set_iface_perm(uaa->device, uaa->info.bIfaceIndex,
+ UID_ROOT, GID_OPERATOR, 0644);
+
+ error = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx,
+ &ubtbcmfw_fifo_methods, &sc->sc_fifo,
+ device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex);
+ if (error != 0) {
+ device_printf(dev, "could not attach fifo. %s\n",
+ usb2_errstr(error));
+ goto detach;
+ }
+
+ return (0); /* success */
+
+detach:
+ ubtbcmfw_detach(dev);
+
+ return (ENXIO); /* failure */
+} /* ubtbcmfw_attach */
+
+/*
+ * Detach the device
+ */
+
+static int
+ubtbcmfw_detach(device_t dev)
+{
+ struct ubtbcmfw_softc *sc = device_get_softc(dev);
+
+ usb2_fifo_detach(&sc->sc_fifo);
+
+ usb2_transfer_unsetup(sc->sc_xfer, UBTBCMFW_N_TRANSFER);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+} /* ubtbcmfw_detach */
+
+/*
+ * USB write callback
+ */
+
+static void
+ubtbcmfw_write_callback(struct usb2_xfer *xfer)
+{
+ struct ubtbcmfw_softc *sc = xfer->priv_sc;
+ struct usb2_fifo *f = sc->sc_fifo.fp[USB_FIFO_TX];
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+setup_next:
+ if (usb2_fifo_get_data(f, xfer->frbuffers, 0,
+ xfer->max_data_length, &actlen, 0)) {
+ xfer->frlengths[0] = actlen;
+ usb2_start_hardware(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (xfer->error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ xfer->flags.stall_pipe = 1;
+ goto setup_next;
+ }
+ break;
+ }
+} /* ubtbcmfw_write_callback */
+
+/*
+ * USB read callback
+ */
+
+static void
+ubtbcmfw_read_callback(struct usb2_xfer *xfer)
+{
+ struct ubtbcmfw_softc *sc = xfer->priv_sc;
+ struct usb2_fifo *fifo = sc->sc_fifo.fp[USB_FIFO_RX];
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ usb2_fifo_put_data(fifo, xfer->frbuffers, 0, xfer->actlen, 1);
+ /* FALLTHROUGH */
+
+ case USB_ST_SETUP:
+setup_next:
+ if (usb2_fifo_put_bytes_max(fifo) > 0) {
+ xfer->frlengths[0] = xfer->max_data_length;
+ usb2_start_hardware(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (xfer->error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ xfer->flags.stall_pipe = 1;
+ goto setup_next;
+ }
+ break;
+ }
+} /* ubtbcmfw_read_callback */
+
+/*
+ * Called when we about to start read()ing from the device
+ */
+
+static void
+ubtbcmfw_start_read(struct usb2_fifo *fifo)
+{
+ struct ubtbcmfw_softc *sc = fifo->priv_sc0;
+
+ usb2_transfer_start(sc->sc_xfer[UBTBCMFW_INTR_DT_RD]);
+} /* ubtbcmfw_start_read */
+
+/*
+ * Called when we about to stop reading (i.e. closing fifo)
+ */
+
+static void
+ubtbcmfw_stop_read(struct usb2_fifo *fifo)
+{
+ struct ubtbcmfw_softc *sc = fifo->priv_sc0;
+
+ usb2_transfer_stop(sc->sc_xfer[UBTBCMFW_INTR_DT_RD]);
+} /* ubtbcmfw_stop_read */
+
+/*
+ * Called when we about to start write()ing to the device, poll()ing
+ * for write or flushing fifo
+ */
+
+static void
+ubtbcmfw_start_write(struct usb2_fifo *fifo)
+{
+ struct ubtbcmfw_softc *sc = fifo->priv_sc0;
+
+ usb2_transfer_start(sc->sc_xfer[UBTBCMFW_BULK_DT_WR]);
+} /* ubtbcmfw_start_write */
+
+/*
+ * Called when we about to stop writing (i.e. closing fifo)
+ */
+
+static void
+ubtbcmfw_stop_write(struct usb2_fifo *fifo)
+{
+ struct ubtbcmfw_softc *sc = fifo->priv_sc0;
+
+ usb2_transfer_stop(sc->sc_xfer[UBTBCMFW_BULK_DT_WR]);
+} /* ubtbcmfw_stop_write */
+
+/*
+ * Called when fifo is open
+ */
+
+static int
+ubtbcmfw_open(struct usb2_fifo *fifo, int fflags, struct thread *td)
+{
+ struct ubtbcmfw_softc *sc = fifo->priv_sc0;
+ struct usb2_xfer *xfer;
+
+ /*
+ * f_open fifo method can only be called with either FREAD
+ * or FWRITE flag set at one time.
+ */
+
+ if (fflags & FREAD)
+ xfer = sc->sc_xfer[UBTBCMFW_INTR_DT_RD];
+ else if (fflags & FWRITE)
+ xfer = sc->sc_xfer[UBTBCMFW_BULK_DT_WR];
+ else
+ return (EINVAL); /* should not happen */
+
+ if (usb2_fifo_alloc_buffer(fifo, xfer->max_data_length,
+ UBTBCMFW_IFQ_MAXLEN) != 0)
+ return (ENOMEM);
+
+ return (0);
+} /* ubtbcmfw_open */
+
+/*
+ * Called when fifo is closed
+ */
+
+static void
+ubtbcmfw_close(struct usb2_fifo *fifo, int fflags, struct thread *td)
+{
+ if (fflags & (FREAD | FWRITE))
+ usb2_fifo_free_buffer(fifo);
+} /* ubtbcmfw_close */
+
+/*
+ * Process ioctl() on USB device
+ */
+
+static int
+ubtbcmfw_ioctl(struct usb2_fifo *fifo, u_long cmd, void *data,
+ int fflags, struct thread *td)
+{
+ struct ubtbcmfw_softc *sc = fifo->priv_sc0;
+ int error = 0;
+
+ switch (cmd) {
+ case USB_GET_DEVICE_DESC:
+ memcpy(data, usb2_get_device_descriptor(sc->sc_udev),
+ sizeof(struct usb2_device_descriptor));
+ break;
+
+ default:
+ error = EINVAL;
+ break;
+ }
+
+ return (error);
+} /* ubtbcmfw_ioctl */
diff --git a/sys/dev/usb/controller/at91dci.c b/sys/dev/usb/controller/at91dci.c
new file mode 100644
index 000000000000..a7283b4c663d
--- /dev/null
+++ b/sys/dev/usb/controller/at91dci.c
@@ -0,0 +1,2467 @@
+#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 <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.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_sw_transfer.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)) - \
+ USB_P2U(&(((struct at91dci_softc *)0)->sc_bus))))
+
+#define AT9100_DCI_PC2SC(pc) \
+ AT9100_DCI_BUS2SC((pc)->tag_parent->info->bus)
+
+#if USB_DEBUG
+static int at91dcidebug = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, at91dci, CTLFLAG_RW, 0, "USB at91dci");
+SYSCTL_INT(_hw_usb2_at91dci, OID_AUTO, debug, CTLFLAG_RW,
+ &at91dcidebug, 0, "at91dci debug level");
+#endif
+
+#define AT9100_DCI_INTR_ENDPT 1
+
+/* prototypes */
+
+struct usb2_bus_methods at91dci_bus_methods;
+struct usb2_pipe_methods at91dci_device_bulk_methods;
+struct usb2_pipe_methods at91dci_device_ctrl_methods;
+struct usb2_pipe_methods at91dci_device_intr_methods;
+struct usb2_pipe_methods at91dci_device_isoc_fs_methods;
+struct usb2_pipe_methods at91dci_root_ctrl_methods;
+struct usb2_pipe_methods at91dci_root_intr_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 usb2_xfer *, usb2_error_t);
+static void at91dci_do_poll(struct usb2_bus *);
+static void at91dci_root_ctrl_poll(struct at91dci_softc *);
+static void at91dci_standard_done(struct usb2_xfer *);
+
+static usb2_sw_transfer_func_t at91dci_root_intr_done;
+static usb2_sw_transfer_func_t at91dci_root_ctrl_done;
+
+/*
+ * 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 usb2_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 usb2_device *udev,
+ const struct usb2_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 usb2_xfer *xfer)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+ uint8_t use_polling;
+
+ if (!(sc->sc_flags.status_suspend)) {
+ return;
+ }
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ AT91_UDP_WRITE_4(sc, AT91_UDP_GSTATE, AT91_UDP_GSTATE_ESR);
+
+ /* wait 8 milliseconds */
+ if (use_polling) {
+ /* polling */
+ DELAY(8000);
+ } else {
+ /* Wait for reset to complete. */
+ usb2_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 usb2_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)) {
+ /* abort any ongoing transfer */
+ if (!td->did_stall) {
+ DPRINTFN(5, "stalling\n");
+ temp |= AT91_UDP_CSR_FORCESTALL;
+ td->did_stall = 1;
+ }
+ goto not_complete;
+ }
+ /* 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 */
+ usb2_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:
+ /* 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 usb2_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) {
+ usb2_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 usb2_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) {
+
+ usb2_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 usb2_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->endpoint & 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 usb2_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 */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &at91dci_root_intr_done);
+ }
+ } 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 */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &at91dci_root_intr_done);
+ }
+ }
+ 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 */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &at91dci_root_intr_done);
+ }
+ /* 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 = 0;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+}
+
+static void
+at91dci_setup_standard_chain(struct usb2_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->endpoint),
+ xfer->sumlen, usb2_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.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.offset = 0;
+
+ sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+ ep_no = (xfer->endpoint & 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;
+
+ at91dci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ if (x != xfer->nframes) {
+ if (xfer->endpoint & 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) {
+ 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;
+ }
+ }
+
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+
+ /* check if we need to sync */
+ if (need_sync && xfer->flags_int.control_xfr) {
+
+ /* we need a SYNC point after TX */
+ temp.func = &at91dci_data_tx_sync;
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ at91dci_setup_standard_chain_sub(&temp);
+ }
+ /* check if we should append a status stage */
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ if (xfer->endpoint & UE_DIR_IN) {
+ temp.func = &at91dci_data_rx;
+ need_sync = 0;
+ } else {
+ temp.func = &at91dci_data_tx;
+ need_sync = 1;
+ }
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ at91dci_setup_standard_chain_sub(&temp);
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &at91dci_data_tx_sync;
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ 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 usb2_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 usb2_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->endpoint & 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 */
+ usb2_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usb2_transfer_timeout_ms(xfer,
+ &at91dci_timeout, xfer->timeout);
+ }
+ }
+}
+
+static void
+at91dci_root_intr_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(9, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_PRE_DATA) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ at91dci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* setup buffer */
+ std->ptr = sc->sc_hub_idata;
+ std->len = sizeof(sc->sc_hub_idata);
+
+ /* set port bit */
+ sc->sc_hub_idata[0] = 0x02; /* we only have one port */
+
+done:
+ return;
+}
+
+static usb2_error_t
+at91dci_standard_done_sub(struct usb2_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 usb2_xfer *xfer)
+{
+ usb2_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+ /* 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 usb2_xfer *xfer, usb2_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, pipe=%p, error=%d\n",
+ xfer, xfer->pipe, error);
+
+ if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) {
+ ep_no = (xfer->endpoint & 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 */
+ usb2_transfer_done(xfer, error);
+}
+
+static void
+at91dci_set_stall(struct usb2_device *udev, struct usb2_xfer *xfer,
+ struct usb2_pipe *pipe)
+{
+ struct at91dci_softc *sc;
+ uint32_t csr_val;
+ uint8_t csr_reg;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(5, "pipe=%p\n", pipe);
+
+ if (xfer) {
+ /* cancel any ongoing transfers */
+ at91dci_device_done(xfer, USB_ERR_STALLED);
+ }
+ /* set FORCESTALL */
+ sc = AT9100_DCI_BUS2SC(udev->bus);
+ csr_reg = (pipe->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 usb2_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 usb2_device *udev, struct usb2_pipe *pipe)
+{
+ struct at91dci_softc *sc;
+ struct usb2_endpoint_descriptor *ed;
+
+ DPRINTFN(5, "pipe=%p\n", pipe);
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ /* check mode */
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ /* get softc */
+ sc = AT9100_DCI_BUS2SC(udev->bus);
+
+ /* get endpoint descriptor */
+ ed = pipe->edesc;
+
+ /* reset endpoint */
+ at91dci_clear_stall_sub(sc,
+ (ed->bEndpointAddress & UE_ADDR),
+ (ed->bmAttributes & UE_XFERTYPE),
+ (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb2_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 */
+ usb2_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);
+}
+
+void
+at91dci_suspend(struct at91dci_softc *sc)
+{
+ return;
+}
+
+void
+at91dci_resume(struct at91dci_softc *sc)
+{
+ return;
+}
+
+static void
+at91dci_do_poll(struct usb2_bus *bus)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ at91dci_interrupt_poll(sc);
+ at91dci_root_ctrl_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * at91dci bulk support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_bulk_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_bulk_close(struct usb2_xfer *xfer)
+{
+ at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_device_bulk_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_bulk_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ at91dci_setup_standard_chain(xfer);
+ at91dci_start_standard_chain(xfer);
+}
+
+struct usb2_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,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci control support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_ctrl_close(struct usb2_xfer *xfer)
+{
+ at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_device_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_ctrl_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ at91dci_setup_standard_chain(xfer);
+ at91dci_start_standard_chain(xfer);
+}
+
+struct usb2_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,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_intr_close(struct usb2_xfer *xfer)
+{
+ at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_device_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_intr_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ at91dci_setup_standard_chain(xfer);
+ at91dci_start_standard_chain(xfer);
+}
+
+struct usb2_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,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_isoc_fs_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_isoc_fs_close(struct usb2_xfer *xfer)
+{
+ at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_device_isoc_fs_enter(struct usb2_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->pipe->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->pipe->isoc_next) & AT91_UDP_FRM_MASK;
+
+ if ((xfer->pipe->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->pipe->isoc_next = (nframes + 3) & AT91_UDP_FRM_MASK;
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ temp = (xfer->pipe->isoc_next - nframes) & AT91_UDP_FRM_MASK;
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ usb2_isoc_time_expand(&sc->sc_bus, nframes) + temp +
+ xfer->nframes;
+
+ /* compute frame number for next insertion */
+ xfer->pipe->isoc_next += xfer->nframes;
+
+ /* setup TDs */
+ at91dci_setup_standard_chain(xfer);
+}
+
+static void
+at91dci_device_isoc_fs_start(struct usb2_xfer *xfer)
+{
+ /* start TD chain */
+ at91dci_start_standard_chain(xfer);
+}
+
+struct usb2_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,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci root control support
+ *------------------------------------------------------------------------*
+ * simulate a hardware HUB by handling
+ * all the necessary requests
+ *------------------------------------------------------------------------*/
+
+static void
+at91dci_root_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_root_ctrl_close(struct usb2_xfer *xfer)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_ctrl.xfer == xfer) {
+ sc->sc_root_ctrl.xfer = NULL;
+ }
+ at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+/*
+ * USB descriptors for the virtual Root HUB:
+ */
+
+static const struct usb2_device_descriptor at91dci_devd = {
+ .bLength = sizeof(struct usb2_device_descriptor),
+ .bDescriptorType = UDESC_DEVICE,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_HSHUBSTT,
+ .bMaxPacketSize = 64,
+ .bcdDevice = {0x00, 0x01},
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .bNumConfigurations = 1,
+};
+
+static const struct usb2_device_qualifier at91dci_odevd = {
+ .bLength = sizeof(struct usb2_device_qualifier),
+ .bDescriptorType = UDESC_DEVICE_QUALIFIER,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_FSHUB,
+ .bMaxPacketSize0 = 0,
+ .bNumConfigurations = 0,
+};
+
+static const struct at91dci_config_desc at91dci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb2_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 usb2_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = UIPROTO_HSHUBSTT,
+ },
+
+ .endpd = {
+ .bLength = sizeof(struct usb2_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = (UE_DIR_IN | AT9100_DCI_INTR_ENDPT),
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8,
+ .bInterval = 255,
+ },
+};
+
+static const struct usb2_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 void
+at91dci_root_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_root_ctrl_start(struct usb2_xfer *xfer)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_ctrl.xfer = xfer;
+
+ usb2_bus_roothub_exec(xfer->xroot->bus);
+}
+
+static void
+at91dci_root_ctrl_task(struct usb2_bus *bus)
+{
+ at91dci_root_ctrl_poll(AT9100_DCI_BUS2SC(bus));
+}
+
+static void
+at91dci_root_ctrl_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+ uint16_t value;
+ uint16_t index;
+ uint8_t use_polling;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_SETUP) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ at91dci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* buffer reset */
+ std->ptr = USB_ADD_BYTES(&sc->sc_hub_temp, 0);
+ std->len = 0;
+
+ value = UGETW(std->req.wValue);
+ index = UGETW(std->req.wIndex);
+
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ /* demultiplex the control request */
+
+ switch (std->req.bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (std->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 (std->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 (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (UGETW(std->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(std->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 (std->req.bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_INTERFACE:
+ switch (std->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 (std->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 (std->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 (std->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 (std->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 (std->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;
+ }
+ std->len = sizeof(at91dci_devd);
+ std->ptr = USB_ADD_BYTES(&at91dci_devd, 0);
+ goto tr_valid;
+ case UDESC_CONFIG:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ std->len = sizeof(at91dci_confd);
+ std->ptr = USB_ADD_BYTES(&at91dci_confd, 0);
+ goto tr_valid;
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ std->len = sizeof(at91dci_langtab);
+ std->ptr = USB_ADD_BYTES(&at91dci_langtab, 0);
+ goto tr_valid;
+
+ case 1: /* Vendor */
+ std->len = sizeof(at91dci_vendor);
+ std->ptr = USB_ADD_BYTES(&at91dci_vendor, 0);
+ goto tr_valid;
+
+ case 2: /* Product */
+ std->len = sizeof(at91dci_product);
+ std->ptr = USB_ADD_BYTES(&at91dci_product, 0);
+ goto tr_valid;
+ default:
+ break;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_stalled;
+
+tr_handle_get_config:
+ std->len = 1;
+ sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+ goto tr_valid;
+
+tr_handle_get_status:
+ std->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:
+ std->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:
+ std->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(xfer);
+ 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:
+ std->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:
+ std->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 */
+ bzero(sc->sc_ep_flags, sizeof(sc->sc_ep_flags));
+ }
+ }
+ if (sc->sc_flags.change_suspend) {
+ value |= UPS_C_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortChange, value);
+ std->len = sizeof(sc->sc_hub_temp.ps);
+ goto tr_valid;
+
+tr_handle_get_class_descriptor:
+ if (value & 0xFF) {
+ goto tr_stalled;
+ }
+ std->ptr = USB_ADD_BYTES(&at91dci_hubd, 0);
+ std->len = sizeof(at91dci_hubd);
+ goto tr_valid;
+
+tr_stalled:
+ std->err = USB_ERR_STALLED;
+tr_valid:
+done:
+ return;
+}
+
+static void
+at91dci_root_ctrl_poll(struct at91dci_softc *sc)
+{
+ usb2_sw_transfer(&sc->sc_root_ctrl,
+ &at91dci_root_ctrl_done);
+}
+
+struct usb2_pipe_methods at91dci_root_ctrl_methods =
+{
+ .open = at91dci_root_ctrl_open,
+ .close = at91dci_root_ctrl_close,
+ .enter = at91dci_root_ctrl_enter,
+ .start = at91dci_root_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 0,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci root interrupt support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_root_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_root_intr_close(struct usb2_xfer *xfer)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_intr.xfer == xfer) {
+ sc->sc_root_intr.xfer = NULL;
+ }
+ at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_root_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_root_intr_start(struct usb2_xfer *xfer)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_intr.xfer = xfer;
+}
+
+struct usb2_pipe_methods at91dci_root_intr_methods =
+{
+ .open = at91dci_root_intr_open,
+ .close = at91dci_root_intr_close,
+ .enter = at91dci_root_intr_enter,
+ .start = at91dci_root_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+static void
+at91dci_xfer_setup(struct usb2_setup_params *parm)
+{
+ const struct usb2_hw_ep_profile *pf;
+ struct at91dci_softc *sc;
+ struct usb2_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;
+
+ usb2_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 "usb2_transfer_setup_sub" set an error
+ */
+ if (parm->err) {
+ return;
+ }
+ /*
+ * allocate transfer descriptors
+ */
+ last_obj = NULL;
+
+ /*
+ * get profile stuff
+ */
+ if (ntd) {
+
+ ep_no = xfer->endpoint & 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 usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc,
+ struct usb2_pipe *pipe)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ pipe, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb2_mode,
+ sc->sc_rt_addr);
+
+ if (udev->device_index == sc->sc_rt_addr) {
+
+ if (udev->flags.usb2_mode != USB_MODE_HOST) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bEndpointAddress) {
+ case USB_CONTROL_ENDPOINT:
+ pipe->methods = &at91dci_root_ctrl_methods;
+ break;
+ case UE_DIR_IN | AT9100_DCI_INTR_ENDPT:
+ pipe->methods = &at91dci_root_intr_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ } else {
+
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ if (udev->speed != USB_SPEED_FULL) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ pipe->methods = &at91dci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ pipe->methods = &at91dci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ pipe->methods = &at91dci_device_isoc_fs_methods;
+ break;
+ case UE_BULK:
+ pipe->methods = &at91dci_device_bulk_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+struct usb2_bus_methods at91dci_bus_methods =
+{
+ .pipe_init = &at91dci_pipe_init,
+ .xfer_setup = &at91dci_xfer_setup,
+ .xfer_unsetup = &at91dci_xfer_unsetup,
+ .do_poll = &at91dci_do_poll,
+ .get_hw_ep_profile = &at91dci_get_hw_ep_profile,
+ .set_stall = &at91dci_set_stall,
+ .clear_stall = &at91dci_clear_stall,
+ .roothub_exec = &at91dci_root_ctrl_task,
+};
diff --git a/sys/dev/usb/controller/at91dci.h b/sys/dev/usb/controller/at91dci.h
new file mode 100644
index 000000000000..d38630731827
--- /dev/null
+++ b/sys/dev/usb/controller/at91dci.h
@@ -0,0 +1,245 @@
+/* $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 usb2_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 usb2_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;
+};
+
+struct at91dci_config_desc {
+ struct usb2_config_descriptor confd;
+ struct usb2_interface_descriptor ifcd;
+ struct usb2_endpoint_descriptor endpd;
+} __packed;
+
+union at91dci_hub_temp {
+ uWord wValue;
+ struct usb2_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 usb2_bus sc_bus;
+ union at91dci_hub_temp sc_hub_temp;
+ LIST_HEAD(, usb2_xfer) sc_interrupt_list_head;
+ struct usb2_sw_transfer sc_root_ctrl;
+ struct usb2_sw_transfer sc_root_intr;
+
+ struct usb2_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 */
+
+usb2_error_t at91dci_init(struct at91dci_softc *sc);
+void at91dci_uninit(struct at91dci_softc *sc);
+void at91dci_suspend(struct at91dci_softc *sc);
+void at91dci_resume(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/dev/usb/controller/at91dci_atmelarm.c b/sys/dev/usb/controller/at91dci_atmelarm.c
new file mode 100644
index 000000000000..71d2937cdde5
--- /dev/null
+++ b/sys/dev/usb/controller/at91dci_atmelarm.c
@@ -0,0 +1,347 @@
+#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 <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.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;
+static device_shutdown_t at91_udp_shutdown;
+
+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 (usb2_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 */
+ usb2_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, (void *)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,
+ (void *)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, (void *)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,
+ (void *)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_all_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;
+ }
+ usb2_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 int
+at91_udp_shutdown(device_t dev)
+{
+ struct at91_udp_softc *sc = device_get_softc(dev);
+ int err;
+
+ err = bus_generic_shutdown(dev);
+ if (err)
+ return (err);
+
+ at91dci_uninit(&sc->sc_dci);
+
+ 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_shutdown, at91_udp_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+};
+
+static driver_t at91_udp_driver = {
+ "at91_udp",
+ at91_udp_methods,
+ 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/dev/usb/controller/atmegadci.c b/sys/dev/usb/controller/atmegadci.c
new file mode 100644
index 000000000000..8a6882248159
--- /dev/null
+++ b/sys/dev/usb/controller/atmegadci.c
@@ -0,0 +1,2327 @@
+#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 Device
+ * Controller
+ */
+
+/*
+ * NOTE: When the chip detects BUS-reset it will also reset the
+ * endpoints, Function-address and more.
+ */
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.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_sw_transfer.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)) - \
+ USB_P2U(&(((struct atmegadci_softc *)0)->sc_bus))))
+
+#define ATMEGA_PC2SC(pc) \
+ ATMEGA_BUS2SC((pc)->tag_parent->info->bus)
+
+#if USB_DEBUG
+static int atmegadci_debug = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, atmegadci, CTLFLAG_RW, 0, "USB ATMEGA DCI");
+SYSCTL_INT(_hw_usb2_atmegadci, OID_AUTO, debug, CTLFLAG_RW,
+ &atmegadci_debug, 0, "ATMEGA DCI debug level");
+#endif
+
+#define ATMEGA_INTR_ENDPT 1
+
+/* prototypes */
+
+struct usb2_bus_methods atmegadci_bus_methods;
+struct usb2_pipe_methods atmegadci_device_bulk_methods;
+struct usb2_pipe_methods atmegadci_device_ctrl_methods;
+struct usb2_pipe_methods atmegadci_device_intr_methods;
+struct usb2_pipe_methods atmegadci_device_isoc_fs_methods;
+struct usb2_pipe_methods atmegadci_root_ctrl_methods;
+struct usb2_pipe_methods atmegadci_root_intr_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 usb2_xfer *, usb2_error_t);
+static void atmegadci_do_poll(struct usb2_bus *);
+static void atmegadci_root_ctrl_poll(struct atmegadci_softc *);
+static void atmegadci_standard_done(struct usb2_xfer *);
+
+static usb2_sw_transfer_func_t atmegadci_root_intr_done;
+static usb2_sw_transfer_func_t atmegadci_root_ctrl_done;
+
+/*
+ * Here is a list of what the chip supports:
+ */
+static const struct usb2_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_multi_buffer = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_isochronous = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+};
+
+static void
+atmegadci_get_hw_ep_profile(struct usb2_device *udev,
+ const struct usb2_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 usb2_xfer *xfer)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+ uint8_t use_polling;
+ uint8_t temp;
+
+ if (!sc->sc_flags.status_suspend) {
+ return;
+ }
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ temp = ATMEGA_READ_1(sc, ATMEGA_UDCON);
+ ATMEGA_WRITE_1(sc, ATMEGA_UDCON, temp | ATMEGA_UDCON_RMWKUP);
+
+ /* wait 8 milliseconds */
+ if (use_polling) {
+ /* polling */
+ DELAY(8000);
+ } else {
+ /* Wait for reset to complete. */
+ usb2_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);
+
+ ATMEGA_WRITE_1(sc, ATMEGA_UDADDR, 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 usb2_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)) {
+ /* 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;
+ }
+ 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 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 */
+ usb2_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;
+ } else {
+ sc->sc_dv_addr = 0xFF;
+ }
+
+ /* clear SETUP packet interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ~ATMEGA_UEINTX_RXSTPI);
+ return (0); /* complete */
+
+not_complete:
+ /* 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 usb2_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) {
+ usb2_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 usb2_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 */
+ }
+ if (!(temp & (ATMEGA_UEINTX_FIFOCON |
+ ATMEGA_UEINTX_TXINI))) {
+ /* cannot write any data */
+ 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) {
+
+ usb2_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!
+ */
+ if (!(temp & (ATMEGA_UEINTX_FIFOCON |
+ ATMEGA_UEINTX_TXINI))) {
+ /* cannot write any data */
+ 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 usb2_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 usb2_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 */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &atmegadci_root_intr_done);
+ }
+ } 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 */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &atmegadci_root_intr_done);
+ }
+ }
+}
+
+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);
+
+ /* 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 */
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &atmegadci_root_intr_done);
+ }
+ /*
+ * 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_EORSMI) {
+
+ 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 */
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &atmegadci_root_intr_done);
+ }
+ } 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_EORSMI |
+ ATMEGA_UDINT_EORSTE);
+
+ /* complete root HUB interrupt endpoint */
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &atmegadci_root_intr_done);
+ }
+ }
+ /* check VBUS */
+ status = ATMEGA_READ_1(sc, ATMEGA_USBINT);
+
+ /* clear all set interrupts */
+ ATMEGA_WRITE_1(sc, ATMEGA_USBINT, ~status);
+
+ if (status & ATMEGA_USBINT_VBUSTI) {
+ uint8_t temp;
+
+ 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);
+
+ /* clear all set interrupts */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINT, ~status);
+
+ if (status) {
+
+ DPRINTFN(5, "real endpoint interrupt 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 = 0;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+}
+
+static void
+atmegadci_setup_standard_chain(struct usb2_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->endpoint),
+ xfer->sumlen, usb2_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.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.offset = 0;
+
+ sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+ ep_no = (xfer->endpoint & 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;
+
+ atmegadci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ if (x != xfer->nframes) {
+ if (xfer->endpoint & 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) {
+ 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;
+ }
+ }
+
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+
+ /* check if we need to sync */
+ if (need_sync && xfer->flags_int.control_xfr) {
+
+ /* we need a SYNC point after TX */
+ temp.func = &atmegadci_data_tx_sync;
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ atmegadci_setup_standard_chain_sub(&temp);
+ }
+ /* check if we should append a status stage */
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ if (xfer->endpoint & UE_DIR_IN) {
+ temp.func = &atmegadci_data_rx;
+ need_sync = 0;
+ } else {
+ temp.func = &atmegadci_data_tx;
+ need_sync = 1;
+ }
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ atmegadci_setup_standard_chain_sub(&temp);
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &atmegadci_data_tx_sync;
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ 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 usb2_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 usb2_xfer *xfer)
+{
+ DPRINTFN(9, "\n");
+
+ /* poll one time - will turn on interrupts */
+ if (atmegadci_xfer_do_fifo(xfer)) {
+
+ /* put transfer on interrupt queue */
+ usb2_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usb2_transfer_timeout_ms(xfer,
+ &atmegadci_timeout, xfer->timeout);
+ }
+ }
+}
+
+static void
+atmegadci_root_intr_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(9, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_PRE_DATA) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ atmegadci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* setup buffer */
+ std->ptr = sc->sc_hub_idata;
+ std->len = sizeof(sc->sc_hub_idata);
+
+ /* set port bit */
+ sc->sc_hub_idata[0] = 0x02; /* we only have one port */
+
+done:
+ return;
+}
+
+static usb2_error_t
+atmegadci_standard_done_sub(struct usb2_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 usb2_xfer *xfer)
+{
+ usb2_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+ /* 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 usb2_xfer *xfer, usb2_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(2, "xfer=%p, pipe=%p, error=%d\n",
+ xfer, xfer->pipe, error);
+
+ if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) {
+ ep_no = (xfer->endpoint & 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 */
+ usb2_transfer_done(xfer, error);
+}
+
+static void
+atmegadci_set_stall(struct usb2_device *udev, struct usb2_xfer *xfer,
+ struct usb2_pipe *pipe)
+{
+ struct atmegadci_softc *sc;
+ uint8_t ep_no;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(5, "pipe=%p\n", pipe);
+
+ if (xfer) {
+ /* cancel any ongoing transfers */
+ atmegadci_device_done(xfer, USB_ERR_STALLED);
+ }
+ sc = ATMEGA_BUS2SC(udev->bus);
+ /* get endpoint number */
+ ep_no = (pipe->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);
+
+ if (ep_type == UE_CONTROL) {
+ /* 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(7));
+ } else {
+ temp = 0;
+ 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_EPBK1 |
+ ATMEGA_UECFG1X_EPSIZE(7));
+
+ temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X);
+ if (!(temp & ATMEGA_UESTA0X_CFGOK)) {
+ DPRINTFN(0, "Chip rejected configuration\n");
+ }
+ }
+}
+
+static void
+atmegadci_clear_stall(struct usb2_device *udev, struct usb2_pipe *pipe)
+{
+ struct atmegadci_softc *sc;
+ struct usb2_endpoint_descriptor *ed;
+
+ DPRINTFN(5, "pipe=%p\n", pipe);
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ /* check mode */
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ /* get softc */
+ sc = ATMEGA_BUS2SC(udev->bus);
+
+ /* get endpoint descriptor */
+ ed = pipe->edesc;
+
+ /* reset endpoint */
+ atmegadci_clear_stall_sub(sc,
+ (ed->bEndpointAddress & UE_ADDR),
+ (ed->bmAttributes & UE_XFERTYPE),
+ (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb2_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);
+
+ /* enable USB PAD regulator */
+ ATMEGA_WRITE_1(sc, ATMEGA_UHWCON,
+ ATMEGA_UHWCON_UVREGE);
+
+ /* turn on clocks */
+ (sc->sc_clocks_on) (&sc->sc_bus);
+
+ /* wait a little for things to stabilise */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+
+ /* 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 = 1; 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);
+
+ 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);
+}
+
+void
+atmegadci_suspend(struct atmegadci_softc *sc)
+{
+ return;
+}
+
+void
+atmegadci_resume(struct atmegadci_softc *sc)
+{
+ return;
+}
+
+static void
+atmegadci_do_poll(struct usb2_bus *bus)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ atmegadci_interrupt_poll(sc);
+ atmegadci_root_ctrl_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * at91dci bulk support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_bulk_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_bulk_close(struct usb2_xfer *xfer)
+{
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_device_bulk_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_bulk_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ atmegadci_setup_standard_chain(xfer);
+ atmegadci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods atmegadci_device_bulk_methods =
+{
+ .open = atmegadci_device_bulk_open,
+ .close = atmegadci_device_bulk_close,
+ .enter = atmegadci_device_bulk_enter,
+ .start = atmegadci_device_bulk_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci control support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_ctrl_close(struct usb2_xfer *xfer)
+{
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_device_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_ctrl_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ atmegadci_setup_standard_chain(xfer);
+ atmegadci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods atmegadci_device_ctrl_methods =
+{
+ .open = atmegadci_device_ctrl_open,
+ .close = atmegadci_device_ctrl_close,
+ .enter = atmegadci_device_ctrl_enter,
+ .start = atmegadci_device_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_intr_close(struct usb2_xfer *xfer)
+{
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_device_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_intr_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ atmegadci_setup_standard_chain(xfer);
+ atmegadci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods atmegadci_device_intr_methods =
+{
+ .open = atmegadci_device_intr_open,
+ .close = atmegadci_device_intr_close,
+ .enter = atmegadci_device_intr_enter,
+ .start = atmegadci_device_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_isoc_fs_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_isoc_fs_close(struct usb2_xfer *xfer)
+{
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_device_isoc_fs_enter(struct usb2_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->pipe->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->pipe->isoc_next) & ATMEGA_FRAME_MASK;
+
+ if ((xfer->pipe->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->pipe->isoc_next = (nframes + 3) & ATMEGA_FRAME_MASK;
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ temp = (xfer->pipe->isoc_next - nframes) & ATMEGA_FRAME_MASK;
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ usb2_isoc_time_expand(&sc->sc_bus, nframes) + temp +
+ xfer->nframes;
+
+ /* compute frame number for next insertion */
+ xfer->pipe->isoc_next += xfer->nframes;
+
+ /* setup TDs */
+ atmegadci_setup_standard_chain(xfer);
+}
+
+static void
+atmegadci_device_isoc_fs_start(struct usb2_xfer *xfer)
+{
+ /* start TD chain */
+ atmegadci_start_standard_chain(xfer);
+}
+
+struct usb2_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,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci root control support
+ *------------------------------------------------------------------------*
+ * simulate a hardware HUB by handling
+ * all the necessary requests
+ *------------------------------------------------------------------------*/
+
+static void
+atmegadci_root_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_root_ctrl_close(struct usb2_xfer *xfer)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_ctrl.xfer == xfer) {
+ sc->sc_root_ctrl.xfer = NULL;
+ }
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+/*
+ * USB descriptors for the virtual Root HUB:
+ */
+
+static const struct usb2_device_descriptor atmegadci_devd = {
+ .bLength = sizeof(struct usb2_device_descriptor),
+ .bDescriptorType = UDESC_DEVICE,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_HSHUBSTT,
+ .bMaxPacketSize = 64,
+ .bcdDevice = {0x00, 0x01},
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .bNumConfigurations = 1,
+};
+
+static const struct usb2_device_qualifier atmegadci_odevd = {
+ .bLength = sizeof(struct usb2_device_qualifier),
+ .bDescriptorType = UDESC_DEVICE_QUALIFIER,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_FSHUB,
+ .bMaxPacketSize0 = 0,
+ .bNumConfigurations = 0,
+};
+
+static const struct atmegadci_config_desc atmegadci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb2_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 usb2_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = UIPROTO_HSHUBSTT,
+ },
+
+ .endpd = {
+ .bLength = sizeof(struct usb2_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = (UE_DIR_IN | ATMEGA_INTR_ENDPT),
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8,
+ .bInterval = 255,
+ },
+};
+
+static const struct usb2_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 void
+atmegadci_root_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_root_ctrl_start(struct usb2_xfer *xfer)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_ctrl.xfer = xfer;
+
+ usb2_bus_roothub_exec(xfer->xroot->bus);
+}
+
+static void
+atmegadci_root_ctrl_task(struct usb2_bus *bus)
+{
+ atmegadci_root_ctrl_poll(ATMEGA_BUS2SC(bus));
+}
+
+static void
+atmegadci_root_ctrl_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+ uint16_t value;
+ uint16_t index;
+ uint8_t use_polling;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_SETUP) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ atmegadci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* buffer reset */
+ std->ptr = USB_ADD_BYTES(&sc->sc_hub_temp, 0);
+ std->len = 0;
+
+ value = UGETW(std->req.wValue);
+ index = UGETW(std->req.wIndex);
+
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ /* demultiplex the control request */
+
+ switch (std->req.bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (std->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 (std->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 (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (UGETW(std->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(std->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 (std->req.bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_INTERFACE:
+ switch (std->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 (std->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 (std->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 (std->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 (std->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 (std->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;
+ }
+ std->len = sizeof(atmegadci_devd);
+ std->ptr = USB_ADD_BYTES(&atmegadci_devd, 0);
+ goto tr_valid;
+ case UDESC_CONFIG:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ std->len = sizeof(atmegadci_confd);
+ std->ptr = USB_ADD_BYTES(&atmegadci_confd, 0);
+ goto tr_valid;
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ std->len = sizeof(atmegadci_langtab);
+ std->ptr = USB_ADD_BYTES(&atmegadci_langtab, 0);
+ goto tr_valid;
+
+ case 1: /* Vendor */
+ std->len = sizeof(atmegadci_vendor);
+ std->ptr = USB_ADD_BYTES(&atmegadci_vendor, 0);
+ goto tr_valid;
+
+ case 2: /* Product */
+ std->len = sizeof(atmegadci_product);
+ std->ptr = USB_ADD_BYTES(&atmegadci_product, 0);
+ goto tr_valid;
+ default:
+ break;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_stalled;
+
+tr_handle_get_config:
+ std->len = 1;
+ sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+ goto tr_valid;
+
+tr_handle_get_status:
+ std->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:
+ std->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:
+ std->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(xfer);
+ 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:
+ sc->sc_flags.change_connect = 0;
+ break;
+ case UHF_C_PORT_SUSPEND:
+ sc->sc_flags.change_suspend = 0;
+ break;
+ default:
+ std->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:
+ std->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);
+ std->len = sizeof(sc->sc_hub_temp.ps);
+ goto tr_valid;
+
+tr_handle_get_class_descriptor:
+ if (value & 0xFF) {
+ goto tr_stalled;
+ }
+ std->ptr = USB_ADD_BYTES(&atmegadci_hubd, 0);
+ std->len = sizeof(atmegadci_hubd);
+ goto tr_valid;
+
+tr_stalled:
+ std->err = USB_ERR_STALLED;
+tr_valid:
+done:
+ return;
+}
+
+static void
+atmegadci_root_ctrl_poll(struct atmegadci_softc *sc)
+{
+ usb2_sw_transfer(&sc->sc_root_ctrl,
+ &atmegadci_root_ctrl_done);
+}
+
+struct usb2_pipe_methods atmegadci_root_ctrl_methods =
+{
+ .open = atmegadci_root_ctrl_open,
+ .close = atmegadci_root_ctrl_close,
+ .enter = atmegadci_root_ctrl_enter,
+ .start = atmegadci_root_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 0,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci root interrupt support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_root_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_root_intr_close(struct usb2_xfer *xfer)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_intr.xfer == xfer) {
+ sc->sc_root_intr.xfer = NULL;
+ }
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_root_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_root_intr_start(struct usb2_xfer *xfer)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_intr.xfer = xfer;
+}
+
+struct usb2_pipe_methods atmegadci_root_intr_methods =
+{
+ .open = atmegadci_root_intr_open,
+ .close = atmegadci_root_intr_close,
+ .enter = atmegadci_root_intr_enter,
+ .start = atmegadci_root_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+static void
+atmegadci_xfer_setup(struct usb2_setup_params *parm)
+{
+ const struct usb2_hw_ep_profile *pf;
+ struct atmegadci_softc *sc;
+ struct usb2_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;
+
+ usb2_transfer_setup_sub(parm);
+
+ /*
+ * compute maximum number of TDs
+ */
+ if (parm->methods == &atmegadci_device_ctrl_methods) {
+
+ ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */
+ + 1 /* SYNC 2 */ ;
+
+ } else if (parm->methods == &atmegadci_device_bulk_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &atmegadci_device_intr_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &atmegadci_device_isoc_fs_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else {
+
+ ntd = 0;
+ }
+
+ /*
+ * check if "usb2_transfer_setup_sub" set an error
+ */
+ if (parm->err) {
+ return;
+ }
+ /*
+ * allocate transfer descriptors
+ */
+ last_obj = NULL;
+
+ /*
+ * get profile stuff
+ */
+ if (ntd) {
+
+ ep_no = xfer->endpoint & UE_ADDR;
+ atmegadci_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 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 usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc,
+ struct usb2_pipe *pipe)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ pipe, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb2_mode,
+ sc->sc_rt_addr);
+
+ if (udev->device_index == sc->sc_rt_addr) {
+
+ if (udev->flags.usb2_mode != USB_MODE_HOST) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bEndpointAddress) {
+ case USB_CONTROL_ENDPOINT:
+ pipe->methods = &atmegadci_root_ctrl_methods;
+ break;
+ case UE_DIR_IN | ATMEGA_INTR_ENDPT:
+ pipe->methods = &atmegadci_root_intr_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ } else {
+
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ if (udev->speed != USB_SPEED_FULL) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ pipe->methods = &atmegadci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ pipe->methods = &atmegadci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ pipe->methods = &atmegadci_device_isoc_fs_methods;
+ break;
+ case UE_BULK:
+ pipe->methods = &atmegadci_device_bulk_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+struct usb2_bus_methods atmegadci_bus_methods =
+{
+ .pipe_init = &atmegadci_pipe_init,
+ .xfer_setup = &atmegadci_xfer_setup,
+ .xfer_unsetup = &atmegadci_xfer_unsetup,
+ .do_poll = &atmegadci_do_poll,
+ .get_hw_ep_profile = &atmegadci_get_hw_ep_profile,
+ .set_stall = &atmegadci_set_stall,
+ .clear_stall = &atmegadci_clear_stall,
+ .roothub_exec = &atmegadci_root_ctrl_task,
+};
diff --git a/sys/dev/usb/controller/atmegadci.h b/sys/dev/usb/controller/atmegadci.h
new file mode 100644
index 000000000000..90b3334131c2
--- /dev/null
+++ b/sys/dev/usb/controller/atmegadci.h
@@ -0,0 +1,273 @@
+/* $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)
+
+#ifndef ATMEGA_HAVE_BUS_SPACE
+#define ATMEGA_HAVE_BUS_SPACE 1
+#endif
+
+#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_USBINT 0xDA
+#define ATMEGA_USBINT_VBUSTI (1 << 0) /* USB VBUS 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_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_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 usb2_bus *);
+
+struct atmegadci_td {
+ struct atmegadci_td *obj_next;
+ atmegadci_cmd_t *func;
+ struct usb2_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 usb2_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;
+};
+
+struct atmegadci_config_desc {
+ struct usb2_config_descriptor confd;
+ struct usb2_interface_descriptor ifcd;
+ struct usb2_endpoint_descriptor endpd;
+} __packed;
+
+union atmegadci_hub_temp {
+ uWord wValue;
+ struct usb2_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 usb2_bus sc_bus;
+ union atmegadci_hub_temp sc_hub_temp;
+ LIST_HEAD(, usb2_xfer) sc_interrupt_list_head;
+ struct usb2_sw_transfer sc_root_ctrl;
+ struct usb2_sw_transfer sc_root_intr;
+
+ /* must be set by by the bus interface layer */
+ atmegadci_clocks_t *sc_clocks_on;
+ atmegadci_clocks_t *sc_clocks_off;
+
+ struct usb2_device *sc_devices[ATMEGA_MAX_DEVICES];
+ struct resource *sc_irq_res;
+ void *sc_intr_hdl;
+#if (ATMEGA_HAVE_BUS_SPACE != 0)
+ struct resource *sc_io_res;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+#endif
+ 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 */
+
+usb2_error_t atmegadci_init(struct atmegadci_softc *sc);
+void atmegadci_uninit(struct atmegadci_softc *sc);
+void atmegadci_suspend(struct atmegadci_softc *sc);
+void atmegadci_resume(struct atmegadci_softc *sc);
+void atmegadci_interrupt(struct atmegadci_softc *sc);
+
+#endif /* _ATMEGADCI_H_ */
diff --git a/sys/dev/usb/controller/atmegadci_atmelarm.c b/sys/dev/usb/controller/atmegadci_atmelarm.c
new file mode 100644
index 000000000000..e63f5cc26490
--- /dev/null
+++ b/sys/dev/usb/controller/atmegadci_atmelarm.c
@@ -0,0 +1,27 @@
+#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.
+ */
diff --git a/sys/dev/usb/controller/ehci.c b/sys/dev/usb/controller/ehci.c
new file mode 100644
index 000000000000..5802268f3424
--- /dev/null
+++ b/sys/dev/usb/controller/ehci.c
@@ -0,0 +1,3965 @@
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 2004 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 2004 Lennart Augustsson. All rights reserved.
+ * Copyright (c) 2004 Charles M. Hannum. 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 Enhanced Host Controller Driver, a.k.a. USB 2.0 controller.
+ *
+ * The EHCI 0.96 spec can be found at
+ * http://developer.intel.com/technology/usb/download/ehci-r096.pdf
+ * The EHCI 1.0 spec can be found at
+ * http://developer.intel.com/technology/usb/download/ehci-r10.pdf
+ * and the USB 2.0 spec at
+ * http://www.usb.org/developers/docs/usb_20.zip
+ *
+ */
+
+/*
+ * TODO:
+ * 1) command failures are not recovered correctly
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.h>
+
+#define USB_DEBUG_VAR ehcidebug
+
+#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_sw_transfer.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/ehci.h>
+
+#define EHCI_BUS2SC(bus) ((ehci_softc_t *)(((uint8_t *)(bus)) - \
+ USB_P2U(&(((ehci_softc_t *)0)->sc_bus))))
+
+#if USB_DEBUG
+static int ehcidebug = 0;
+static int ehcinohighspeed = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, ehci, CTLFLAG_RW, 0, "USB ehci");
+SYSCTL_INT(_hw_usb2_ehci, OID_AUTO, debug, CTLFLAG_RW,
+ &ehcidebug, 0, "Debug level");
+SYSCTL_INT(_hw_usb2_ehci, OID_AUTO, no_hs, CTLFLAG_RW,
+ &ehcinohighspeed, 0, "Disable High Speed USB");
+
+static void ehci_dump_regs(ehci_softc_t *sc);
+static void ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *sqh);
+
+#endif
+
+#define EHCI_INTR_ENDPT 1
+
+extern struct usb2_bus_methods ehci_bus_methods;
+extern struct usb2_pipe_methods ehci_device_bulk_methods;
+extern struct usb2_pipe_methods ehci_device_ctrl_methods;
+extern struct usb2_pipe_methods ehci_device_intr_methods;
+extern struct usb2_pipe_methods ehci_device_isoc_fs_methods;
+extern struct usb2_pipe_methods ehci_device_isoc_hs_methods;
+extern struct usb2_pipe_methods ehci_root_ctrl_methods;
+extern struct usb2_pipe_methods ehci_root_intr_methods;
+
+static void ehci_do_poll(struct usb2_bus *bus);
+static void ehci_root_ctrl_poll(ehci_softc_t *sc);
+static void ehci_device_done(struct usb2_xfer *xfer, usb2_error_t error);
+static uint8_t ehci_check_transfer(struct usb2_xfer *xfer);
+static void ehci_timeout(void *arg);
+
+static usb2_sw_transfer_func_t ehci_root_intr_done;
+static usb2_sw_transfer_func_t ehci_root_ctrl_done;
+
+struct ehci_std_temp {
+ ehci_softc_t *sc;
+ struct usb2_page_cache *pc;
+ ehci_qtd_t *td;
+ ehci_qtd_t *td_next;
+ uint32_t average;
+ uint32_t qtd_status;
+ uint32_t len;
+ uint16_t max_frame_size;
+ uint8_t shortpkt;
+ uint8_t auto_data_toggle;
+ uint8_t setup_alt_next;
+ uint8_t short_frames_ok;
+};
+
+/*
+ * Byte-order conversion functions.
+ */
+static uint32_t
+htoehci32(ehci_softc_t *sc, const uint32_t v)
+{
+ return ((sc->sc_flags & EHCI_SCFLG_BIGEDESC) ?
+ htobe32(v) : htole32(v));
+}
+
+static uint32_t
+ehci32toh(ehci_softc_t *sc, const uint32_t v)
+{
+ return ((sc->sc_flags & EHCI_SCFLG_BIGEDESC) ?
+ be32toh(v) : le32toh(v));
+}
+
+void
+ehci_iterate_hw_softc(struct usb2_bus *bus, usb2_bus_mem_sub_cb_t *cb)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(bus);
+ uint32_t i;
+
+ cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg,
+ sizeof(uint32_t) * EHCI_FRAMELIST_COUNT, EHCI_FRAMELIST_ALIGN);
+
+ cb(bus, &sc->sc_hw.async_start_pc, &sc->sc_hw.async_start_pg,
+ sizeof(ehci_qh_t), EHCI_QH_ALIGN);
+
+ for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.intr_start_pc + i,
+ sc->sc_hw.intr_start_pg + i,
+ sizeof(ehci_qh_t), EHCI_QH_ALIGN);
+ }
+
+ for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.isoc_hs_start_pc + i,
+ sc->sc_hw.isoc_hs_start_pg + i,
+ sizeof(ehci_itd_t), EHCI_ITD_ALIGN);
+ }
+
+ for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.isoc_fs_start_pc + i,
+ sc->sc_hw.isoc_fs_start_pg + i,
+ sizeof(ehci_sitd_t), EHCI_SITD_ALIGN);
+ }
+}
+
+static usb2_error_t
+ehci_hc_reset(ehci_softc_t *sc)
+{
+ uint32_t hcr;
+ uint32_t n;
+
+ EOWRITE4(sc, EHCI_USBCMD, 0); /* Halt controller */
+
+ for (n = 0; n != 100; n++) {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+ hcr = EOREAD4(sc, EHCI_USBSTS);
+ if (hcr & EHCI_STS_HCH) {
+ hcr = 0;
+ break;
+ }
+ }
+
+ /*
+ * Fall through and try reset anyway even though
+ * Table 2-9 in the EHCI spec says this will result
+ * in undefined behavior.
+ */
+
+ EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET);
+ for (n = 0; n != 100; n++) {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+ hcr = EOREAD4(sc, EHCI_USBCMD);
+ if (!(hcr & EHCI_CMD_HCRESET)) {
+ if (sc->sc_flags & EHCI_SCFLG_SETMODE)
+ EOWRITE4(sc, 0x68, 0x3);
+ hcr = 0;
+ break;
+ }
+ }
+
+ if (hcr) {
+ return (USB_ERR_IOERROR);
+ }
+ return (0);
+}
+
+usb2_error_t
+ehci_init(ehci_softc_t *sc)
+{
+ struct usb2_page_search buf_res;
+ uint32_t version;
+ uint32_t sparams;
+ uint32_t cparams;
+ uint32_t hcr;
+ uint16_t i;
+ uint16_t x;
+ uint16_t y;
+ uint16_t bit;
+ usb2_error_t err = 0;
+
+ DPRINTF("start\n");
+
+ usb2_callout_init_mtx(&sc->sc_tmo_pcd, &sc->sc_bus.bus_mtx, 0);
+
+#if USB_DEBUG
+ if (ehcidebug > 2) {
+ ehci_dump_regs(sc);
+ }
+#endif
+
+ sc->sc_offs = EREAD1(sc, EHCI_CAPLENGTH);
+
+ version = EREAD2(sc, EHCI_HCIVERSION);
+ device_printf(sc->sc_bus.bdev, "EHCI version %x.%x\n",
+ version >> 8, version & 0xff);
+
+ sparams = EREAD4(sc, EHCI_HCSPARAMS);
+ DPRINTF("sparams=0x%x\n", sparams);
+
+ sc->sc_noport = EHCI_HCS_N_PORTS(sparams);
+ cparams = EREAD4(sc, EHCI_HCCPARAMS);
+ DPRINTF("cparams=0x%x\n", cparams);
+
+ if (EHCI_HCC_64BIT(cparams)) {
+ DPRINTF("HCC uses 64-bit structures\n");
+
+ /* MUST clear segment register if 64 bit capable */
+ EWRITE4(sc, EHCI_CTRLDSSEGMENT, 0);
+ }
+ sc->sc_bus.usbrev = USB_REV_2_0;
+
+ /* Reset the controller */
+ DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev));
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ err = ehci_hc_reset(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ if (err) {
+ device_printf(sc->sc_bus.bdev, "reset timeout\n");
+ return (err);
+ }
+ /*
+ * use current frame-list-size selection 0: 1024*4 bytes 1: 512*4
+ * bytes 2: 256*4 bytes 3: unknown
+ */
+ if (EHCI_CMD_FLS(EOREAD4(sc, EHCI_USBCMD)) == 3) {
+ device_printf(sc->sc_bus.bdev, "invalid frame-list-size\n");
+ return (USB_ERR_IOERROR);
+ }
+ /* set up the bus struct */
+ sc->sc_bus.methods = &ehci_bus_methods;
+
+ sc->sc_eintrs = EHCI_NORMAL_INTRS;
+
+ for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ ehci_qh_t *qh;
+
+ usb2_get_page(sc->sc_hw.intr_start_pc + i, 0, &buf_res);
+
+ qh = buf_res.buffer;
+
+ /* initialize page cache pointer */
+
+ qh->page_cache = sc->sc_hw.intr_start_pc + i;
+
+ /* store a pointer to queue head */
+
+ sc->sc_intr_p_last[i] = qh;
+
+ qh->qh_self =
+ htoehci32(sc, buf_res.physaddr) |
+ htoehci32(sc, EHCI_LINK_QH);
+
+ qh->qh_endp =
+ htoehci32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH));
+ qh->qh_endphub =
+ htoehci32(sc, EHCI_QH_SET_MULT(1));
+ qh->qh_curqtd = 0;
+
+ qh->qh_qtd.qtd_next =
+ htoehci32(sc, EHCI_LINK_TERMINATE);
+ qh->qh_qtd.qtd_altnext =
+ htoehci32(sc, EHCI_LINK_TERMINATE);
+ qh->qh_qtd.qtd_status =
+ htoehci32(sc, EHCI_QTD_HALTED);
+ }
+
+ /*
+ * the QHs are arranged to give poll intervals that are
+ * powers of 2 times 1ms
+ */
+ bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2;
+ while (bit) {
+ x = bit;
+ while (x & bit) {
+ ehci_qh_t *qh_x;
+ ehci_qh_t *qh_y;
+
+ y = (x ^ bit) | (bit / 2);
+
+ qh_x = sc->sc_intr_p_last[x];
+ qh_y = sc->sc_intr_p_last[y];
+
+ /*
+ * the next QH has half the poll interval
+ */
+ qh_x->qh_link = qh_y->qh_self;
+
+ x++;
+ }
+ bit >>= 1;
+ }
+
+ if (1) {
+ ehci_qh_t *qh;
+
+ qh = sc->sc_intr_p_last[0];
+
+ /* the last (1ms) QH terminates */
+ qh->qh_link = htoehci32(sc, EHCI_LINK_TERMINATE);
+ }
+ for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ ehci_sitd_t *sitd;
+ ehci_itd_t *itd;
+
+ usb2_get_page(sc->sc_hw.isoc_fs_start_pc + i, 0, &buf_res);
+
+ sitd = buf_res.buffer;
+
+ /* initialize page cache pointer */
+
+ sitd->page_cache = sc->sc_hw.isoc_fs_start_pc + i;
+
+ /* store a pointer to the transfer descriptor */
+
+ sc->sc_isoc_fs_p_last[i] = sitd;
+
+ /* initialize full speed isochronous */
+
+ sitd->sitd_self =
+ htoehci32(sc, buf_res.physaddr) |
+ htoehci32(sc, EHCI_LINK_SITD);
+
+ sitd->sitd_back =
+ htoehci32(sc, EHCI_LINK_TERMINATE);
+
+ sitd->sitd_next =
+ sc->sc_intr_p_last[i | (EHCI_VIRTUAL_FRAMELIST_COUNT / 2)]->qh_self;
+
+
+ usb2_get_page(sc->sc_hw.isoc_hs_start_pc + i, 0, &buf_res);
+
+ itd = buf_res.buffer;
+
+ /* initialize page cache pointer */
+
+ itd->page_cache = sc->sc_hw.isoc_hs_start_pc + i;
+
+ /* store a pointer to the transfer descriptor */
+
+ sc->sc_isoc_hs_p_last[i] = itd;
+
+ /* initialize high speed isochronous */
+
+ itd->itd_self =
+ htoehci32(sc, buf_res.physaddr) |
+ htoehci32(sc, EHCI_LINK_ITD);
+
+ itd->itd_next =
+ sitd->sitd_self;
+ }
+
+ usb2_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
+
+ if (1) {
+ uint32_t *pframes;
+
+ pframes = buf_res.buffer;
+
+ /*
+ * execution order:
+ * pframes -> high speed isochronous ->
+ * full speed isochronous -> interrupt QH's
+ */
+ for (i = 0; i < EHCI_FRAMELIST_COUNT; i++) {
+ pframes[i] = sc->sc_isoc_hs_p_last
+ [i & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1)]->itd_self;
+ }
+ }
+ /* setup sync list pointer */
+ EOWRITE4(sc, EHCI_PERIODICLISTBASE, buf_res.physaddr);
+
+ usb2_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res);
+
+ if (1) {
+
+ ehci_qh_t *qh;
+
+ qh = buf_res.buffer;
+
+ /* initialize page cache pointer */
+
+ qh->page_cache = &sc->sc_hw.async_start_pc;
+
+ /* store a pointer to the queue head */
+
+ sc->sc_async_p_last = qh;
+
+ /* init dummy QH that starts the async list */
+
+ qh->qh_self =
+ htoehci32(sc, buf_res.physaddr) |
+ htoehci32(sc, EHCI_LINK_QH);
+
+ /* fill the QH */
+ qh->qh_endp =
+ htoehci32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | EHCI_QH_HRECL);
+ qh->qh_endphub = htoehci32(sc, EHCI_QH_SET_MULT(1));
+ qh->qh_link = qh->qh_self;
+ qh->qh_curqtd = 0;
+
+ /* fill the overlay qTD */
+ qh->qh_qtd.qtd_next = htoehci32(sc, EHCI_LINK_TERMINATE);
+ qh->qh_qtd.qtd_altnext = htoehci32(sc, EHCI_LINK_TERMINATE);
+ qh->qh_qtd.qtd_status = htoehci32(sc, EHCI_QTD_HALTED);
+ }
+ /* flush all cache into memory */
+
+ usb2_bus_mem_flush_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+#if USB_DEBUG
+ if (ehcidebug) {
+ ehci_dump_sqh(sc, sc->sc_async_p_last);
+ }
+#endif
+
+ /* setup async list pointer */
+ EOWRITE4(sc, EHCI_ASYNCLISTADDR, buf_res.physaddr | EHCI_LINK_QH);
+
+
+ /* enable interrupts */
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ /* turn on controller */
+ EOWRITE4(sc, EHCI_USBCMD,
+ EHCI_CMD_ITC_1 | /* 1 microframes interrupt delay */
+ (EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_FLS_M) |
+ EHCI_CMD_ASE |
+ EHCI_CMD_PSE |
+ EHCI_CMD_RS);
+
+ /* Take over port ownership */
+ EOWRITE4(sc, EHCI_CONFIGFLAG, EHCI_CONF_CF);
+
+ for (i = 0; i < 100; i++) {
+ usb2_pause_mtx(NULL, hz / 1000);
+ hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH;
+ if (!hcr) {
+ break;
+ }
+ }
+ if (hcr) {
+ device_printf(sc->sc_bus.bdev, "run timeout\n");
+ return (USB_ERR_IOERROR);
+ }
+
+ if (!err) {
+ /* catch any lost interrupts */
+ ehci_do_poll(&sc->sc_bus);
+ }
+ return (err);
+}
+
+/*
+ * shut down the controller when the system is going down
+ */
+void
+ehci_detach(ehci_softc_t *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ usb2_callout_stop(&sc->sc_tmo_pcd);
+
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ if (ehci_hc_reset(sc)) {
+ DPRINTF("reset failed!\n");
+ }
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* XXX let stray task complete */
+ usb2_pause_mtx(NULL, hz / 20);
+
+ usb2_callout_drain(&sc->sc_tmo_pcd);
+}
+
+void
+ehci_suspend(ehci_softc_t *sc)
+{
+ uint32_t cmd;
+ uint32_t hcr;
+ uint8_t i;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ for (i = 1; i <= sc->sc_noport; i++) {
+ cmd = EOREAD4(sc, EHCI_PORTSC(i));
+ if (((cmd & EHCI_PS_PO) == 0) &&
+ ((cmd & EHCI_PS_PE) == EHCI_PS_PE)) {
+ EOWRITE4(sc, EHCI_PORTSC(i),
+ cmd | EHCI_PS_SUSP);
+ }
+ }
+
+ sc->sc_cmd = EOREAD4(sc, EHCI_USBCMD);
+
+ cmd = sc->sc_cmd & ~(EHCI_CMD_ASE | EHCI_CMD_PSE);
+ EOWRITE4(sc, EHCI_USBCMD, cmd);
+
+ for (i = 0; i < 100; i++) {
+ hcr = EOREAD4(sc, EHCI_USBSTS) &
+ (EHCI_STS_ASS | EHCI_STS_PSS);
+
+ if (hcr == 0) {
+ break;
+ }
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+ }
+
+ if (hcr != 0) {
+ device_printf(sc->sc_bus.bdev, "reset timeout\n");
+ }
+ cmd &= ~EHCI_CMD_RS;
+ EOWRITE4(sc, EHCI_USBCMD, cmd);
+
+ for (i = 0; i < 100; i++) {
+ hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH;
+ if (hcr == EHCI_STS_HCH) {
+ break;
+ }
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+ }
+
+ if (hcr != EHCI_STS_HCH) {
+ device_printf(sc->sc_bus.bdev,
+ "config timeout\n");
+ }
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+ehci_resume(ehci_softc_t *sc)
+{
+ struct usb2_page_search buf_res;
+ uint32_t cmd;
+ uint32_t hcr;
+ uint8_t i;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* restore things in case the bios doesn't */
+ EOWRITE4(sc, EHCI_CTRLDSSEGMENT, 0);
+
+ usb2_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
+ EOWRITE4(sc, EHCI_PERIODICLISTBASE, buf_res.physaddr);
+
+ usb2_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res);
+ EOWRITE4(sc, EHCI_ASYNCLISTADDR, buf_res.physaddr | EHCI_LINK_QH);
+
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ hcr = 0;
+ for (i = 1; i <= sc->sc_noport; i++) {
+ cmd = EOREAD4(sc, EHCI_PORTSC(i));
+ if (((cmd & EHCI_PS_PO) == 0) &&
+ ((cmd & EHCI_PS_SUSP) == EHCI_PS_SUSP)) {
+ EOWRITE4(sc, EHCI_PORTSC(i),
+ cmd | EHCI_PS_FPR);
+ hcr = 1;
+ }
+ }
+
+ if (hcr) {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_RESUME_WAIT));
+
+ for (i = 1; i <= sc->sc_noport; i++) {
+ cmd = EOREAD4(sc, EHCI_PORTSC(i));
+ if (((cmd & EHCI_PS_PO) == 0) &&
+ ((cmd & EHCI_PS_SUSP) == EHCI_PS_SUSP)) {
+ EOWRITE4(sc, EHCI_PORTSC(i),
+ cmd & ~EHCI_PS_FPR);
+ }
+ }
+ }
+ EOWRITE4(sc, EHCI_USBCMD, sc->sc_cmd);
+
+ for (i = 0; i < 100; i++) {
+ hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH;
+ if (hcr != EHCI_STS_HCH) {
+ break;
+ }
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+ }
+ if (hcr == EHCI_STS_HCH) {
+ device_printf(sc->sc_bus.bdev, "config timeout\n");
+ }
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ usb2_pause_mtx(NULL,
+ USB_MS_TO_TICKS(USB_RESUME_WAIT));
+
+ /* catch any lost interrupts */
+ ehci_do_poll(&sc->sc_bus);
+}
+
+void
+ehci_shutdown(ehci_softc_t *sc)
+{
+ DPRINTF("stopping the HC\n");
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ if (ehci_hc_reset(sc)) {
+ DPRINTF("reset failed!\n");
+ }
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+#if USB_DEBUG
+static void
+ehci_dump_regs(ehci_softc_t *sc)
+{
+ uint32_t i;
+
+ i = EOREAD4(sc, EHCI_USBCMD);
+ printf("cmd=0x%08x\n", i);
+
+ if (i & EHCI_CMD_ITC_1)
+ printf(" EHCI_CMD_ITC_1\n");
+ if (i & EHCI_CMD_ITC_2)
+ printf(" EHCI_CMD_ITC_2\n");
+ if (i & EHCI_CMD_ITC_4)
+ printf(" EHCI_CMD_ITC_4\n");
+ if (i & EHCI_CMD_ITC_8)
+ printf(" EHCI_CMD_ITC_8\n");
+ if (i & EHCI_CMD_ITC_16)
+ printf(" EHCI_CMD_ITC_16\n");
+ if (i & EHCI_CMD_ITC_32)
+ printf(" EHCI_CMD_ITC_32\n");
+ if (i & EHCI_CMD_ITC_64)
+ printf(" EHCI_CMD_ITC_64\n");
+ if (i & EHCI_CMD_ASPME)
+ printf(" EHCI_CMD_ASPME\n");
+ if (i & EHCI_CMD_ASPMC)
+ printf(" EHCI_CMD_ASPMC\n");
+ if (i & EHCI_CMD_LHCR)
+ printf(" EHCI_CMD_LHCR\n");
+ if (i & EHCI_CMD_IAAD)
+ printf(" EHCI_CMD_IAAD\n");
+ if (i & EHCI_CMD_ASE)
+ printf(" EHCI_CMD_ASE\n");
+ if (i & EHCI_CMD_PSE)
+ printf(" EHCI_CMD_PSE\n");
+ if (i & EHCI_CMD_FLS_M)
+ printf(" EHCI_CMD_FLS_M\n");
+ if (i & EHCI_CMD_HCRESET)
+ printf(" EHCI_CMD_HCRESET\n");
+ if (i & EHCI_CMD_RS)
+ printf(" EHCI_CMD_RS\n");
+
+ i = EOREAD4(sc, EHCI_USBSTS);
+
+ printf("sts=0x%08x\n", i);
+
+ if (i & EHCI_STS_ASS)
+ printf(" EHCI_STS_ASS\n");
+ if (i & EHCI_STS_PSS)
+ printf(" EHCI_STS_PSS\n");
+ if (i & EHCI_STS_REC)
+ printf(" EHCI_STS_REC\n");
+ if (i & EHCI_STS_HCH)
+ printf(" EHCI_STS_HCH\n");
+ if (i & EHCI_STS_IAA)
+ printf(" EHCI_STS_IAA\n");
+ if (i & EHCI_STS_HSE)
+ printf(" EHCI_STS_HSE\n");
+ if (i & EHCI_STS_FLR)
+ printf(" EHCI_STS_FLR\n");
+ if (i & EHCI_STS_PCD)
+ printf(" EHCI_STS_PCD\n");
+ if (i & EHCI_STS_ERRINT)
+ printf(" EHCI_STS_ERRINT\n");
+ if (i & EHCI_STS_INT)
+ printf(" EHCI_STS_INT\n");
+
+ printf("ien=0x%08x\n",
+ EOREAD4(sc, EHCI_USBINTR));
+ printf("frindex=0x%08x ctrdsegm=0x%08x periodic=0x%08x async=0x%08x\n",
+ EOREAD4(sc, EHCI_FRINDEX),
+ EOREAD4(sc, EHCI_CTRLDSSEGMENT),
+ EOREAD4(sc, EHCI_PERIODICLISTBASE),
+ EOREAD4(sc, EHCI_ASYNCLISTADDR));
+ for (i = 1; i <= sc->sc_noport; i++) {
+ printf("port %d status=0x%08x\n", i,
+ EOREAD4(sc, EHCI_PORTSC(i)));
+ }
+}
+
+static void
+ehci_dump_link(ehci_softc_t *sc, uint32_t link, int type)
+{
+ link = ehci32toh(sc, link);
+ printf("0x%08x", link);
+ if (link & EHCI_LINK_TERMINATE)
+ printf("<T>");
+ else {
+ printf("<");
+ if (type) {
+ switch (EHCI_LINK_TYPE(link)) {
+ case EHCI_LINK_ITD:
+ printf("ITD");
+ break;
+ case EHCI_LINK_QH:
+ printf("QH");
+ break;
+ case EHCI_LINK_SITD:
+ printf("SITD");
+ break;
+ case EHCI_LINK_FSTN:
+ printf("FSTN");
+ break;
+ }
+ }
+ printf(">");
+ }
+}
+
+static void
+ehci_dump_qtd(ehci_softc_t *sc, ehci_qtd_t *qtd)
+{
+ uint32_t s;
+
+ printf(" next=");
+ ehci_dump_link(sc, qtd->qtd_next, 0);
+ printf(" altnext=");
+ ehci_dump_link(sc, qtd->qtd_altnext, 0);
+ printf("\n");
+ s = ehci32toh(sc, qtd->qtd_status);
+ printf(" status=0x%08x: toggle=%d bytes=0x%x ioc=%d c_page=0x%x\n",
+ s, EHCI_QTD_GET_TOGGLE(s), EHCI_QTD_GET_BYTES(s),
+ EHCI_QTD_GET_IOC(s), EHCI_QTD_GET_C_PAGE(s));
+ printf(" cerr=%d pid=%d stat=%s%s%s%s%s%s%s%s\n",
+ EHCI_QTD_GET_CERR(s), EHCI_QTD_GET_PID(s),
+ (s & EHCI_QTD_ACTIVE) ? "ACTIVE" : "NOT_ACTIVE",
+ (s & EHCI_QTD_HALTED) ? "-HALTED" : "",
+ (s & EHCI_QTD_BUFERR) ? "-BUFERR" : "",
+ (s & EHCI_QTD_BABBLE) ? "-BABBLE" : "",
+ (s & EHCI_QTD_XACTERR) ? "-XACTERR" : "",
+ (s & EHCI_QTD_MISSEDMICRO) ? "-MISSED" : "",
+ (s & EHCI_QTD_SPLITXSTATE) ? "-SPLIT" : "",
+ (s & EHCI_QTD_PINGSTATE) ? "-PING" : "");
+
+ for (s = 0; s < 5; s++) {
+ printf(" buffer[%d]=0x%08x\n", s,
+ ehci32toh(sc, qtd->qtd_buffer[s]));
+ }
+ for (s = 0; s < 5; s++) {
+ printf(" buffer_hi[%d]=0x%08x\n", s,
+ ehci32toh(sc, qtd->qtd_buffer_hi[s]));
+ }
+}
+
+static uint8_t
+ehci_dump_sqtd(ehci_softc_t *sc, ehci_qtd_t *sqtd)
+{
+ uint8_t temp;
+
+ usb2_pc_cpu_invalidate(sqtd->page_cache);
+ printf("QTD(%p) at 0x%08x:\n", sqtd, ehci32toh(sc, sqtd->qtd_self));
+ ehci_dump_qtd(sc, sqtd);
+ temp = (sqtd->qtd_next & htoehci32(sc, EHCI_LINK_TERMINATE)) ? 1 : 0;
+ return (temp);
+}
+
+static void
+ehci_dump_sqtds(ehci_softc_t *sc, ehci_qtd_t *sqtd)
+{
+ uint16_t i;
+ uint8_t stop;
+
+ stop = 0;
+ for (i = 0; sqtd && (i < 20) && !stop; sqtd = sqtd->obj_next, i++) {
+ stop = ehci_dump_sqtd(sc, sqtd);
+ }
+ if (sqtd) {
+ printf("dump aborted, too many TDs\n");
+ }
+}
+
+static void
+ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *qh)
+{
+ uint32_t endp;
+ uint32_t endphub;
+
+ usb2_pc_cpu_invalidate(qh->page_cache);
+ printf("QH(%p) at 0x%08x:\n", qh, ehci32toh(sc, qh->qh_self) & ~0x1F);
+ printf(" link=");
+ ehci_dump_link(sc, qh->qh_link, 1);
+ printf("\n");
+ endp = ehci32toh(sc, qh->qh_endp);
+ printf(" endp=0x%08x\n", endp);
+ printf(" addr=0x%02x inact=%d endpt=%d eps=%d dtc=%d hrecl=%d\n",
+ EHCI_QH_GET_ADDR(endp), EHCI_QH_GET_INACT(endp),
+ EHCI_QH_GET_ENDPT(endp), EHCI_QH_GET_EPS(endp),
+ EHCI_QH_GET_DTC(endp), EHCI_QH_GET_HRECL(endp));
+ printf(" mpl=0x%x ctl=%d nrl=%d\n",
+ EHCI_QH_GET_MPL(endp), EHCI_QH_GET_CTL(endp),
+ EHCI_QH_GET_NRL(endp));
+ endphub = ehci32toh(sc, qh->qh_endphub);
+ printf(" endphub=0x%08x\n", endphub);
+ printf(" smask=0x%02x cmask=0x%02x huba=0x%02x port=%d mult=%d\n",
+ EHCI_QH_GET_SMASK(endphub), EHCI_QH_GET_CMASK(endphub),
+ EHCI_QH_GET_HUBA(endphub), EHCI_QH_GET_PORT(endphub),
+ EHCI_QH_GET_MULT(endphub));
+ printf(" curqtd=");
+ ehci_dump_link(sc, qh->qh_curqtd, 0);
+ printf("\n");
+ printf("Overlay qTD:\n");
+ ehci_dump_qtd(sc, (void *)&qh->qh_qtd);
+}
+
+static void
+ehci_dump_sitd(ehci_softc_t *sc, ehci_sitd_t *sitd)
+{
+ usb2_pc_cpu_invalidate(sitd->page_cache);
+ printf("SITD(%p) at 0x%08x\n", sitd, ehci32toh(sc, sitd->sitd_self) & ~0x1F);
+ printf(" next=0x%08x\n", ehci32toh(sc, sitd->sitd_next));
+ printf(" portaddr=0x%08x dir=%s addr=%d endpt=0x%x port=0x%x huba=0x%x\n",
+ ehci32toh(sc, sitd->sitd_portaddr),
+ (sitd->sitd_portaddr & htoehci32(sc, EHCI_SITD_SET_DIR_IN))
+ ? "in" : "out",
+ EHCI_SITD_GET_ADDR(ehci32toh(sc, sitd->sitd_portaddr)),
+ EHCI_SITD_GET_ENDPT(ehci32toh(sc, sitd->sitd_portaddr)),
+ EHCI_SITD_GET_PORT(ehci32toh(sc, sitd->sitd_portaddr)),
+ EHCI_SITD_GET_HUBA(ehci32toh(sc, sitd->sitd_portaddr)));
+ printf(" mask=0x%08x\n", ehci32toh(sc, sitd->sitd_mask));
+ printf(" status=0x%08x <%s> len=0x%x\n", ehci32toh(sc, sitd->sitd_status),
+ (sitd->sitd_status & htoehci32(sc, EHCI_SITD_ACTIVE)) ? "ACTIVE" : "",
+ EHCI_SITD_GET_LEN(ehci32toh(sc, sitd->sitd_status)));
+ printf(" back=0x%08x, bp=0x%08x,0x%08x,0x%08x,0x%08x\n",
+ ehci32toh(sc, sitd->sitd_back),
+ ehci32toh(sc, sitd->sitd_bp[0]),
+ ehci32toh(sc, sitd->sitd_bp[1]),
+ ehci32toh(sc, sitd->sitd_bp_hi[0]),
+ ehci32toh(sc, sitd->sitd_bp_hi[1]));
+}
+
+static void
+ehci_dump_itd(ehci_softc_t *sc, ehci_itd_t *itd)
+{
+ usb2_pc_cpu_invalidate(itd->page_cache);
+ printf("ITD(%p) at 0x%08x\n", itd, ehci32toh(sc, itd->itd_self) & ~0x1F);
+ printf(" next=0x%08x\n", ehci32toh(sc, itd->itd_next));
+ printf(" status[0]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[0]),
+ (itd->itd_status[0] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[1]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[1]),
+ (itd->itd_status[1] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[2]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[2]),
+ (itd->itd_status[2] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[3]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[3]),
+ (itd->itd_status[3] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[4]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[4]),
+ (itd->itd_status[4] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[5]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[5]),
+ (itd->itd_status[5] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[6]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[6]),
+ (itd->itd_status[6] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[7]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[7]),
+ (itd->itd_status[7] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" bp[0]=0x%08x\n", ehci32toh(sc, itd->itd_bp[0]));
+ printf(" addr=0x%02x; endpt=0x%01x\n",
+ EHCI_ITD_GET_ADDR(ehci32toh(sc, itd->itd_bp[0])),
+ EHCI_ITD_GET_ENDPT(ehci32toh(sc, itd->itd_bp[0])));
+ printf(" bp[1]=0x%08x\n", ehci32toh(sc, itd->itd_bp[1]));
+ printf(" dir=%s; mpl=0x%02x\n",
+ (ehci32toh(sc, itd->itd_bp[1]) & EHCI_ITD_SET_DIR_IN) ? "in" : "out",
+ EHCI_ITD_GET_MPL(ehci32toh(sc, itd->itd_bp[1])));
+ printf(" bp[2..6]=0x%08x,0x%08x,0x%08x,0x%08x,0x%08x\n",
+ ehci32toh(sc, itd->itd_bp[2]),
+ ehci32toh(sc, itd->itd_bp[3]),
+ ehci32toh(sc, itd->itd_bp[4]),
+ ehci32toh(sc, itd->itd_bp[5]),
+ ehci32toh(sc, itd->itd_bp[6]));
+ printf(" bp_hi=0x%08x,0x%08x,0x%08x,0x%08x,\n"
+ " 0x%08x,0x%08x,0x%08x\n",
+ ehci32toh(sc, itd->itd_bp_hi[0]),
+ ehci32toh(sc, itd->itd_bp_hi[1]),
+ ehci32toh(sc, itd->itd_bp_hi[2]),
+ ehci32toh(sc, itd->itd_bp_hi[3]),
+ ehci32toh(sc, itd->itd_bp_hi[4]),
+ ehci32toh(sc, itd->itd_bp_hi[5]),
+ ehci32toh(sc, itd->itd_bp_hi[6]));
+}
+
+static void
+ehci_dump_isoc(ehci_softc_t *sc)
+{
+ ehci_itd_t *itd;
+ ehci_sitd_t *sitd;
+ uint16_t max = 1000;
+ uint16_t pos;
+
+ pos = (EOREAD4(sc, EHCI_FRINDEX) / 8) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+
+ printf("%s: isochronous dump from frame 0x%03x:\n",
+ __FUNCTION__, pos);
+
+ itd = sc->sc_isoc_hs_p_last[pos];
+ sitd = sc->sc_isoc_fs_p_last[pos];
+
+ while (itd && max && max--) {
+ ehci_dump_itd(sc, itd);
+ itd = itd->prev;
+ }
+
+ while (sitd && max && max--) {
+ ehci_dump_sitd(sc, sitd);
+ sitd = sitd->prev;
+ }
+}
+
+#endif
+
+static void
+ehci_transfer_intr_enqueue(struct usb2_xfer *xfer)
+{
+ /* check for early completion */
+ if (ehci_check_transfer(xfer)) {
+ return;
+ }
+ /* put transfer on interrupt queue */
+ usb2_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usb2_transfer_timeout_ms(xfer, &ehci_timeout, xfer->timeout);
+ }
+}
+
+#define EHCI_APPEND_FS_TD(std,last) (last) = _ehci_append_fs_td(std,last)
+static ehci_sitd_t *
+_ehci_append_fs_td(ehci_sitd_t *std, ehci_sitd_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->next = last->next;
+ std->sitd_next = last->sitd_next;
+
+ std->prev = last;
+
+ usb2_pc_cpu_flush(std->page_cache);
+
+ /*
+ * the last->next->prev is never followed: std->next->prev = std;
+ */
+ last->next = std;
+ last->sitd_next = std->sitd_self;
+
+ usb2_pc_cpu_flush(last->page_cache);
+
+ return (std);
+}
+
+#define EHCI_APPEND_HS_TD(std,last) (last) = _ehci_append_hs_td(std,last)
+static ehci_itd_t *
+_ehci_append_hs_td(ehci_itd_t *std, ehci_itd_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->next = last->next;
+ std->itd_next = last->itd_next;
+
+ std->prev = last;
+
+ usb2_pc_cpu_flush(std->page_cache);
+
+ /*
+ * the last->next->prev is never followed: std->next->prev = std;
+ */
+ last->next = std;
+ last->itd_next = std->itd_self;
+
+ usb2_pc_cpu_flush(last->page_cache);
+
+ return (std);
+}
+
+#define EHCI_APPEND_QH(sqh,last) (last) = _ehci_append_qh(sqh,last)
+static ehci_qh_t *
+_ehci_append_qh(ehci_qh_t *sqh, ehci_qh_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", sqh, last);
+
+ if (sqh->prev != NULL) {
+ /* should not happen */
+ DPRINTFN(0, "QH already linked!\n");
+ return (last);
+ }
+ /* (sc->sc_bus.mtx) must be locked */
+
+ sqh->next = last->next;
+ sqh->qh_link = last->qh_link;
+
+ sqh->prev = last;
+
+ usb2_pc_cpu_flush(sqh->page_cache);
+
+ /*
+ * the last->next->prev is never followed: sqh->next->prev = sqh;
+ */
+
+ last->next = sqh;
+ last->qh_link = sqh->qh_self;
+
+ usb2_pc_cpu_flush(last->page_cache);
+
+ return (sqh);
+}
+
+#define EHCI_REMOVE_FS_TD(std,last) (last) = _ehci_remove_fs_td(std,last)
+static ehci_sitd_t *
+_ehci_remove_fs_td(ehci_sitd_t *std, ehci_sitd_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->prev->next = std->next;
+ std->prev->sitd_next = std->sitd_next;
+
+ usb2_pc_cpu_flush(std->prev->page_cache);
+
+ if (std->next) {
+ std->next->prev = std->prev;
+ usb2_pc_cpu_flush(std->next->page_cache);
+ }
+ return ((last == std) ? std->prev : last);
+}
+
+#define EHCI_REMOVE_HS_TD(std,last) (last) = _ehci_remove_hs_td(std,last)
+static ehci_itd_t *
+_ehci_remove_hs_td(ehci_itd_t *std, ehci_itd_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->prev->next = std->next;
+ std->prev->itd_next = std->itd_next;
+
+ usb2_pc_cpu_flush(std->prev->page_cache);
+
+ if (std->next) {
+ std->next->prev = std->prev;
+ usb2_pc_cpu_flush(std->next->page_cache);
+ }
+ return ((last == std) ? std->prev : last);
+}
+
+#define EHCI_REMOVE_QH(sqh,last) (last) = _ehci_remove_qh(sqh,last)
+static ehci_qh_t *
+_ehci_remove_qh(ehci_qh_t *sqh, ehci_qh_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", sqh, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ /* only remove if not removed from a queue */
+ if (sqh->prev) {
+
+ sqh->prev->next = sqh->next;
+ sqh->prev->qh_link = sqh->qh_link;
+
+ usb2_pc_cpu_flush(sqh->prev->page_cache);
+
+ if (sqh->next) {
+ sqh->next->prev = sqh->prev;
+ usb2_pc_cpu_flush(sqh->next->page_cache);
+ }
+ last = ((last == sqh) ? sqh->prev : last);
+
+ sqh->prev = 0;
+
+ usb2_pc_cpu_flush(sqh->page_cache);
+ }
+ return (last);
+}
+
+static usb2_error_t
+ehci_non_isoc_done_sub(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_qtd_t *td;
+ ehci_qtd_t *td_alt_next;
+ uint32_t status;
+ uint16_t len;
+
+ td = xfer->td_transfer_cache;
+ td_alt_next = td->alt_next;
+
+ if (xfer->aframes != xfer->nframes) {
+ xfer->frlengths[xfer->aframes] = 0;
+ }
+ while (1) {
+
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = ehci32toh(sc, td->qtd_status);
+
+ len = EHCI_QTD_GET_BYTES(status);
+
+ /*
+ * Verify the status length and
+ * add the length to "frlengths[]":
+ */
+ if (len > td->len) {
+ /* should not happen */
+ DPRINTF("Invalid status length, "
+ "0x%04x/0x%04x bytes\n", len, td->len);
+ status |= EHCI_QTD_HALTED;
+ } else if (xfer->aframes != xfer->nframes) {
+ xfer->frlengths[xfer->aframes] += td->len - len;
+ }
+ /* Check for last transfer */
+ if (((void *)td) == xfer->td_transfer_last) {
+ if (len == 0) {
+ /*
+ * Halt is ok if descriptor is last,
+ * and complete:
+ */
+ status &= ~EHCI_QTD_HALTED;
+ }
+ td = NULL;
+ break;
+ }
+ /* Check for transfer error */
+ if (status & EHCI_QTD_HALTED) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ td = td->alt_next;
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ break;
+ }
+ td = td->obj_next;
+
+ if (td->alt_next != td_alt_next) {
+ /* this USB frame is complete */
+ break;
+ }
+ }
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ /* update data toggle */
+
+ xfer->pipe->toggle_next =
+ (status & EHCI_QTD_TOGGLE_MASK) ? 1 : 0;
+
+#if USB_DEBUG
+ if (status & EHCI_QTD_STATERRS) {
+ DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x"
+ "status=%s%s%s%s%s%s%s%s\n",
+ xfer->address, xfer->endpoint, xfer->aframes,
+ (status & EHCI_QTD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]",
+ (status & EHCI_QTD_HALTED) ? "[HALTED]" : "",
+ (status & EHCI_QTD_BUFERR) ? "[BUFERR]" : "",
+ (status & EHCI_QTD_BABBLE) ? "[BABBLE]" : "",
+ (status & EHCI_QTD_XACTERR) ? "[XACTERR]" : "",
+ (status & EHCI_QTD_MISSEDMICRO) ? "[MISSED]" : "",
+ (status & EHCI_QTD_SPLITXSTATE) ? "[SPLIT]" : "",
+ (status & EHCI_QTD_PINGSTATE) ? "[PING]" : "");
+ }
+#endif
+
+ return ((status & EHCI_QTD_HALTED) ?
+ USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+ehci_non_isoc_done(struct usb2_xfer *xfer)
+{
+ usb2_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+#if USB_DEBUG
+ if (ehcidebug > 10) {
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ ehci_dump_sqtds(sc, xfer->td_transfer_first);
+ }
+#endif
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+
+ if (xfer->flags_int.control_hdr) {
+
+ err = ehci_non_isoc_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+
+ err = ehci_non_isoc_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 = ehci_non_isoc_done_sub(xfer);
+ }
+done:
+ ehci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * ehci_check_transfer
+ *
+ * Return values:
+ * 0: USB transfer is not finished
+ * Else: USB transfer is finished
+ *------------------------------------------------------------------------*/
+static uint8_t
+ehci_check_transfer(struct usb2_xfer *xfer)
+{
+ struct usb2_pipe_methods *methods = xfer->pipe->methods;
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ uint32_t status;
+
+ DPRINTFN(13, "xfer=%p checking transfer\n", xfer);
+
+ if (methods == &ehci_device_isoc_fs_methods) {
+ ehci_sitd_t *td;
+
+ /* isochronous full speed transfer */
+
+ td = xfer->td_transfer_last;
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = ehci32toh(sc, td->sitd_status);
+
+ /* also check if first is complete */
+
+ td = xfer->td_transfer_first;
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status |= ehci32toh(sc, td->sitd_status);
+
+ if (!(status & EHCI_SITD_ACTIVE)) {
+ ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
+ goto transferred;
+ }
+ } else if (methods == &ehci_device_isoc_hs_methods) {
+ ehci_itd_t *td;
+
+ /* isochronous high speed transfer */
+
+ td = xfer->td_transfer_last;
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status =
+ td->itd_status[0] | td->itd_status[1] |
+ td->itd_status[2] | td->itd_status[3] |
+ td->itd_status[4] | td->itd_status[5] |
+ td->itd_status[6] | td->itd_status[7];
+
+ /* also check first transfer */
+ td = xfer->td_transfer_first;
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status |=
+ td->itd_status[0] | td->itd_status[1] |
+ td->itd_status[2] | td->itd_status[3] |
+ td->itd_status[4] | td->itd_status[5] |
+ td->itd_status[6] | td->itd_status[7];
+
+ /* if no transactions are active we continue */
+ if (!(status & htoehci32(sc, EHCI_ITD_ACTIVE))) {
+ ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
+ goto transferred;
+ }
+ } else {
+ ehci_qtd_t *td;
+
+ /* non-isochronous transfer */
+
+ /*
+ * check whether there is an error somewhere in the middle,
+ * or whether there was a short packet (SPD and not ACTIVE)
+ */
+ td = xfer->td_transfer_cache;
+
+ while (1) {
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = ehci32toh(sc, td->qtd_status);
+
+ /*
+ * if there is an active TD the transfer isn't done
+ */
+ if (status & EHCI_QTD_ACTIVE) {
+ /* update cache */
+ xfer->td_transfer_cache = td;
+ goto done;
+ }
+ /*
+ * last transfer descriptor makes the transfer done
+ */
+ if (((void *)td) == xfer->td_transfer_last) {
+ break;
+ }
+ /*
+ * any kind of error makes the transfer done
+ */
+ if (status & EHCI_QTD_HALTED) {
+ break;
+ }
+ /*
+ * if there is no alternate next transfer, a short
+ * packet also makes the transfer done
+ */
+ if (EHCI_QTD_GET_BYTES(status)) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ if (td->alt_next) {
+ td = td->alt_next;
+ continue;
+ }
+ }
+ /* transfer is done */
+ break;
+ }
+ td = td->obj_next;
+ }
+ ehci_non_isoc_done(xfer);
+ goto transferred;
+ }
+
+done:
+ DPRINTFN(13, "xfer=%p is still active\n", xfer);
+ return (0);
+
+transferred:
+ return (1);
+}
+
+static void
+ehci_pcd_enable(ehci_softc_t *sc)
+{
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ sc->sc_eintrs |= EHCI_STS_PCD;
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ /* acknowledge any PCD interrupt */
+ EOWRITE4(sc, EHCI_USBSTS, EHCI_STS_PCD);
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &ehci_root_intr_done);
+}
+
+static void
+ehci_interrupt_poll(ehci_softc_t *sc)
+{
+ struct usb2_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ /*
+ * check if transfer is transferred
+ */
+ if (ehci_check_transfer(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * ehci_interrupt - EHCI interrupt handler
+ *
+ * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler,
+ * hence the interrupt handler will be setup before "sc->sc_bus.bdev"
+ * is present !
+ *------------------------------------------------------------------------*/
+void
+ehci_interrupt(ehci_softc_t *sc)
+{
+ uint32_t status;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ DPRINTFN(16, "real interrupt\n");
+
+#if USB_DEBUG
+ if (ehcidebug > 15) {
+ ehci_dump_regs(sc);
+ }
+#endif
+
+ status = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS));
+ if (status == 0) {
+ /* the interrupt was not for us */
+ goto done;
+ }
+ if (!(status & sc->sc_eintrs)) {
+ goto done;
+ }
+ EOWRITE4(sc, EHCI_USBSTS, status); /* acknowledge */
+
+ status &= sc->sc_eintrs;
+
+ if (status & EHCI_STS_HSE) {
+ printf("%s: unrecoverable error, "
+ "controller halted\n", __FUNCTION__);
+#if USB_DEBUG
+ ehci_dump_regs(sc);
+ ehci_dump_isoc(sc);
+#endif
+ }
+ if (status & EHCI_STS_PCD) {
+ /*
+ * Disable PCD interrupt for now, because it will be
+ * on until the port has been reset.
+ */
+ sc->sc_eintrs &= ~EHCI_STS_PCD;
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &ehci_root_intr_done);
+
+ /* do not allow RHSC interrupts > 1 per second */
+ usb2_callout_reset(&sc->sc_tmo_pcd, hz,
+ (void *)&ehci_pcd_enable, sc);
+ }
+ status &= ~(EHCI_STS_INT | EHCI_STS_ERRINT | EHCI_STS_PCD | EHCI_STS_IAA);
+
+ if (status != 0) {
+ /* block unprocessed interrupts */
+ sc->sc_eintrs &= ~status;
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+ printf("%s: blocking interrupts 0x%x\n", __FUNCTION__, status);
+ }
+ /* poll all the USB transfers */
+ ehci_interrupt_poll(sc);
+
+done:
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*
+ * called when a request does not complete
+ */
+static void
+ehci_timeout(void *arg)
+{
+ struct usb2_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ ehci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+ehci_do_poll(struct usb2_bus *bus)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ ehci_interrupt_poll(sc);
+ ehci_root_ctrl_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+ehci_setup_standard_chain_sub(struct ehci_std_temp *temp)
+{
+ struct usb2_page_search buf_res;
+ ehci_qtd_t *td;
+ ehci_qtd_t *td_next;
+ ehci_qtd_t *td_alt_next;
+ uint32_t qtd_altnext;
+ uint32_t buf_offset;
+ uint32_t average;
+ uint32_t len_old;
+ uint8_t shortpkt_old;
+ uint8_t precompute;
+
+ qtd_altnext = htoehci32(temp->sc, EHCI_LINK_TERMINATE);
+ td_alt_next = NULL;
+ buf_offset = 0;
+ shortpkt_old = temp->shortpkt;
+ len_old = temp->len;
+ precompute = 1;
+
+restart:
+
+ td = temp->td;
+ td_next = temp->td_next;
+
+ while (1) {
+
+ if (temp->len == 0) {
+
+ if (temp->shortpkt) {
+ break;
+ }
+ /* send a Zero Length Packet, ZLP, last */
+
+ temp->shortpkt = 1;
+ average = 0;
+
+ } else {
+
+ average = temp->average;
+
+ if (temp->len < average) {
+ if (temp->len % temp->max_frame_size) {
+ temp->shortpkt = 1;
+ }
+ average = temp->len;
+ }
+ }
+
+ if (td_next == NULL) {
+ panic("%s: out of EHCI transfer descriptors!", __FUNCTION__);
+ }
+ /* get next TD */
+
+ td = td_next;
+ td_next = td->obj_next;
+
+ /* check if we are pre-computing */
+
+ if (precompute) {
+
+ /* update remaining length */
+
+ temp->len -= average;
+
+ continue;
+ }
+ /* fill out current TD */
+
+ td->qtd_status =
+ temp->qtd_status |
+ htoehci32(temp->sc, EHCI_QTD_SET_BYTES(average));
+
+ if (average == 0) {
+
+ if (temp->auto_data_toggle == 0) {
+
+ /* update data toggle, ZLP case */
+
+ temp->qtd_status ^=
+ htoehci32(temp->sc, EHCI_QTD_TOGGLE_MASK);
+ }
+ td->len = 0;
+
+ td->qtd_buffer[0] = 0;
+ td->qtd_buffer_hi[0] = 0;
+
+ td->qtd_buffer[1] = 0;
+ td->qtd_buffer_hi[1] = 0;
+
+ } else {
+
+ uint8_t x;
+
+ if (temp->auto_data_toggle == 0) {
+
+ /* update data toggle */
+
+ if (((average + temp->max_frame_size - 1) /
+ temp->max_frame_size) & 1) {
+ temp->qtd_status ^=
+ htoehci32(temp->sc, EHCI_QTD_TOGGLE_MASK);
+ }
+ }
+ td->len = average;
+
+ /* update remaining length */
+
+ temp->len -= average;
+
+ /* fill out buffer pointers */
+
+ usb2_get_page(temp->pc, buf_offset, &buf_res);
+ td->qtd_buffer[0] =
+ htoehci32(temp->sc, buf_res.physaddr);
+ td->qtd_buffer_hi[0] = 0;
+
+ x = 1;
+
+ while (average > EHCI_PAGE_SIZE) {
+ average -= EHCI_PAGE_SIZE;
+ buf_offset += EHCI_PAGE_SIZE;
+ usb2_get_page(temp->pc, buf_offset, &buf_res);
+ td->qtd_buffer[x] =
+ htoehci32(temp->sc,
+ buf_res.physaddr & (~0xFFF));
+ td->qtd_buffer_hi[x] = 0;
+ x++;
+ }
+
+ /*
+ * NOTE: The "average" variable is never zero after
+ * exiting the loop above !
+ *
+ * NOTE: We have to subtract one from the offset to
+ * ensure that we are computing the physical address
+ * of a valid page !
+ */
+ buf_offset += average;
+ usb2_get_page(temp->pc, buf_offset - 1, &buf_res);
+ td->qtd_buffer[x] =
+ htoehci32(temp->sc,
+ buf_res.physaddr & (~0xFFF));
+ td->qtd_buffer_hi[x] = 0;
+ }
+
+ if (td_next) {
+ /* link the current TD with the next one */
+ td->qtd_next = td_next->qtd_self;
+ }
+ td->qtd_altnext = qtd_altnext;
+ td->alt_next = td_alt_next;
+
+ usb2_pc_cpu_flush(td->page_cache);
+ }
+
+ if (precompute) {
+ precompute = 0;
+
+ /* setup alt next pointer, if any */
+ if (temp->short_frames_ok) {
+ if (temp->setup_alt_next) {
+ td_alt_next = td_next;
+ qtd_altnext = td_next->qtd_self;
+ }
+ } else {
+ /* we use this field internally */
+ td_alt_next = td_next;
+ }
+
+ /* restore */
+ temp->shortpkt = shortpkt_old;
+ temp->len = len_old;
+ goto restart;
+ }
+ temp->td = td;
+ temp->td_next = td_next;
+}
+
+static void
+ehci_setup_standard_chain(struct usb2_xfer *xfer, ehci_qh_t **qh_last)
+{
+ struct ehci_std_temp temp;
+ struct usb2_pipe_methods *methods;
+ ehci_qh_t *qh;
+ ehci_qtd_t *td;
+ uint32_t qh_endp;
+ uint32_t qh_endphub;
+ uint32_t x;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpoint),
+ xfer->sumlen, usb2_get_speed(xfer->xroot->udev));
+
+ temp.average = xfer->max_usb2_frame_size;
+ temp.max_frame_size = xfer->max_frame_size;
+ temp.sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ temp.td = NULL;
+ temp.td_next = td;
+ temp.qtd_status = 0;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.short_frames_ok = xfer->flags_int.short_frames_ok;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->pipe->toggle_next) {
+ /* DATA1 is next */
+ temp.qtd_status |=
+ htoehci32(temp.sc, EHCI_QTD_SET_TOGGLE(1));
+ }
+ temp.auto_data_toggle = 0;
+ } else {
+ temp.auto_data_toggle = 1;
+ }
+
+ if (usb2_get_speed(xfer->xroot->udev) != USB_SPEED_HIGH) {
+ /* max 3 retries */
+ temp.qtd_status |=
+ htoehci32(temp.sc, EHCI_QTD_SET_CERR(3));
+ }
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+
+ temp.qtd_status &=
+ htoehci32(temp.sc, EHCI_QTD_SET_CERR(3));
+ temp.qtd_status |= htole32
+ (EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_SETUP) |
+ EHCI_QTD_SET_TOGGLE(0));
+
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.shortpkt = temp.len ? 1 : 0;
+
+ ehci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ while (x != xfer->nframes) {
+
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+ temp.pc = xfer->frbuffers + x;
+
+ x++;
+
+ if (x == xfer->nframes) {
+ temp.setup_alt_next = 0;
+ }
+ /* keep previous data toggle and error count */
+
+ temp.qtd_status &=
+ htoehci32(temp.sc, EHCI_QTD_SET_CERR(3) |
+ EHCI_QTD_SET_TOGGLE(1));
+
+ if (temp.len == 0) {
+
+ /* make sure that we send an USB packet */
+
+ temp.shortpkt = 0;
+
+ } else {
+
+ /* regular data transfer */
+
+ temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ /* set endpoint direction */
+
+ temp.qtd_status |=
+ (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) ?
+ htoehci32(temp.sc, EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_IN)) :
+ htoehci32(temp.sc, EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT));
+
+ ehci_setup_standard_chain_sub(&temp);
+ }
+
+ /* check if we should append a status stage */
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ /*
+ * Send a DATA1 message and invert the current endpoint
+ * direction.
+ */
+
+ temp.qtd_status &= htoehci32(temp.sc, EHCI_QTD_SET_CERR(3) |
+ EHCI_QTD_SET_TOGGLE(1));
+ temp.qtd_status |=
+ (UE_GET_DIR(xfer->endpoint) == UE_DIR_OUT) ?
+ htoehci32(temp.sc, EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_IN) |
+ EHCI_QTD_SET_TOGGLE(1)) :
+ htoehci32(temp.sc, EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT) |
+ EHCI_QTD_SET_TOGGLE(1));
+
+ temp.len = 0;
+ temp.pc = NULL;
+ temp.shortpkt = 0;
+
+ ehci_setup_standard_chain_sub(&temp);
+ }
+ td = temp.td;
+
+ /* the last TD terminates the transfer: */
+ td->qtd_next = htoehci32(temp.sc, EHCI_LINK_TERMINATE);
+ td->qtd_altnext = htoehci32(temp.sc, EHCI_LINK_TERMINATE);
+ td->qtd_status |= htoehci32(temp.sc, EHCI_QTD_IOC);
+
+ usb2_pc_cpu_flush(td->page_cache);
+
+ /* must have at least one frame! */
+
+ xfer->td_transfer_last = td;
+
+#if USB_DEBUG
+ if (ehcidebug > 8) {
+ DPRINTF("nexttog=%d; data before transfer:\n",
+ xfer->pipe->toggle_next);
+ ehci_dump_sqtds(temp.sc,
+ xfer->td_transfer_first);
+ }
+#endif
+
+ methods = xfer->pipe->methods;
+
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ /* the "qh_link" field is filled when the QH is added */
+
+ qh_endp =
+ (EHCI_QH_SET_ADDR(xfer->address) |
+ EHCI_QH_SET_ENDPT(UE_GET_ADDR(xfer->endpoint)) |
+ EHCI_QH_SET_MPL(xfer->max_packet_size));
+
+ if (usb2_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) {
+ qh_endp |= (EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) |
+ EHCI_QH_DTC);
+ if (methods != &ehci_device_intr_methods)
+ qh_endp |= EHCI_QH_SET_NRL(8);
+ } else {
+
+ if (usb2_get_speed(xfer->xroot->udev) == USB_SPEED_FULL) {
+ qh_endp |= (EHCI_QH_SET_EPS(EHCI_QH_SPEED_FULL) |
+ EHCI_QH_DTC);
+ } else {
+ qh_endp |= (EHCI_QH_SET_EPS(EHCI_QH_SPEED_LOW) |
+ EHCI_QH_DTC);
+ }
+
+ if (methods == &ehci_device_ctrl_methods) {
+ qh_endp |= EHCI_QH_CTL;
+ }
+ if (methods != &ehci_device_intr_methods) {
+ /* Only try one time per microframe! */
+ qh_endp |= EHCI_QH_SET_NRL(1);
+ }
+ }
+
+ qh->qh_endp = htoehci32(temp.sc, qh_endp);
+
+ qh_endphub =
+ (EHCI_QH_SET_MULT(xfer->max_packet_count & 3) |
+ EHCI_QH_SET_CMASK(xfer->usb2_cmask) |
+ EHCI_QH_SET_SMASK(xfer->usb2_smask) |
+ EHCI_QH_SET_HUBA(xfer->xroot->udev->hs_hub_addr) |
+ EHCI_QH_SET_PORT(xfer->xroot->udev->hs_port_no));
+
+ qh->qh_endphub = htoehci32(temp.sc, qh_endphub);
+ qh->qh_curqtd = htoehci32(temp.sc, 0);
+
+ /* fill the overlay qTD */
+ qh->qh_qtd.qtd_status = htoehci32(temp.sc, 0);
+
+ if (temp.auto_data_toggle) {
+
+ /* let the hardware compute the data toggle */
+
+ qh->qh_endp &= htoehci32(temp.sc, ~EHCI_QH_DTC);
+
+ if (xfer->pipe->toggle_next) {
+ /* DATA1 is next */
+ qh->qh_qtd.qtd_status |=
+ htoehci32(temp.sc, EHCI_QTD_SET_TOGGLE(1));
+ }
+ }
+ td = xfer->td_transfer_first;
+
+ qh->qh_qtd.qtd_next = td->qtd_self;
+ qh->qh_qtd.qtd_altnext =
+ htoehci32(temp.sc, EHCI_LINK_TERMINATE);
+
+ usb2_pc_cpu_flush(qh->page_cache);
+
+ if (xfer->xroot->udev->pwr_save.suspended == 0) {
+ EHCI_APPEND_QH(qh, *qh_last);
+ }
+}
+
+static void
+ehci_root_intr_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ uint16_t i;
+ uint16_t m;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_PRE_DATA) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ ehci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* setup buffer */
+ std->ptr = sc->sc_hub_idata;
+ std->len = sizeof(sc->sc_hub_idata);
+
+ /* clear any old interrupt data */
+ bzero(sc->sc_hub_idata, sizeof(sc->sc_hub_idata));
+
+ /* set bits */
+ m = (sc->sc_noport + 1);
+ if (m > (8 * sizeof(sc->sc_hub_idata))) {
+ m = (8 * sizeof(sc->sc_hub_idata));
+ }
+ for (i = 1; i < m; i++) {
+ /* pick out CHANGE bits from the status register */
+ if (EOREAD4(sc, EHCI_PORTSC(i)) & EHCI_PS_CLEAR) {
+ sc->sc_hub_idata[i / 8] |= 1 << (i % 8);
+ DPRINTF("port %d changed\n", i);
+ }
+ }
+done:
+ return;
+}
+
+static void
+ehci_isoc_fs_done(ehci_softc_t *sc, struct usb2_xfer *xfer)
+{
+ uint32_t nframes = xfer->nframes;
+ uint32_t status;
+ uint32_t *plen = xfer->frlengths;
+ uint16_t len = 0;
+ ehci_sitd_t *td = xfer->td_transfer_first;
+ ehci_sitd_t **pp_last = &sc->sc_isoc_fs_p_last[xfer->qh_pos];
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_fs_p_last[0];
+ }
+#if USB_DEBUG
+ if (ehcidebug > 15) {
+ DPRINTF("isoc FS-TD\n");
+ ehci_dump_sitd(sc, td);
+ }
+#endif
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = ehci32toh(sc, td->sitd_status);
+
+ len = EHCI_SITD_GET_LEN(status);
+
+ if (*plen >= len) {
+ len = *plen - len;
+ } else {
+ len = 0;
+ }
+
+ *plen = len;
+
+ /* remove FS-TD from schedule */
+ EHCI_REMOVE_FS_TD(td, *pp_last);
+
+ pp_last++;
+ plen++;
+ td = td->obj_next;
+ }
+
+ xfer->aframes = xfer->nframes;
+}
+
+static void
+ehci_isoc_hs_done(ehci_softc_t *sc, struct usb2_xfer *xfer)
+{
+ uint32_t nframes = xfer->nframes;
+ uint32_t status;
+ uint32_t *plen = xfer->frlengths;
+ uint16_t len = 0;
+ uint8_t td_no = 0;
+ ehci_itd_t *td = xfer->td_transfer_first;
+ ehci_itd_t **pp_last = &sc->sc_isoc_hs_p_last[xfer->qh_pos];
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_hs_p_last[0];
+ }
+#if USB_DEBUG
+ if (ehcidebug > 15) {
+ DPRINTF("isoc HS-TD\n");
+ ehci_dump_itd(sc, td);
+ }
+#endif
+
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = ehci32toh(sc, td->itd_status[td_no]);
+
+ len = EHCI_ITD_GET_LEN(status);
+
+ if (*plen >= len) {
+ /*
+ * The length is valid. NOTE: The complete
+ * length is written back into the status
+ * field, and not the remainder like with
+ * other transfer descriptor types.
+ */
+ } else {
+ /* Invalid length - truncate */
+ len = 0;
+ }
+
+ *plen = len;
+
+ plen++;
+ td_no++;
+
+ if ((td_no == 8) || (nframes == 0)) {
+ /* remove HS-TD from schedule */
+ EHCI_REMOVE_HS_TD(td, *pp_last);
+ pp_last++;
+
+ td_no = 0;
+ td = td->obj_next;
+ }
+ }
+ xfer->aframes = xfer->nframes;
+}
+
+/* NOTE: "done" can be run two times in a row,
+ * from close and from interrupt
+ */
+static void
+ehci_device_done(struct usb2_xfer *xfer, usb2_error_t error)
+{
+ struct usb2_pipe_methods *methods = xfer->pipe->methods;
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n",
+ xfer, xfer->pipe, error);
+
+ if ((methods == &ehci_device_bulk_methods) ||
+ (methods == &ehci_device_ctrl_methods)) {
+#if USB_DEBUG
+ if (ehcidebug > 8) {
+ DPRINTF("nexttog=%d; data after transfer:\n",
+ xfer->pipe->toggle_next);
+ ehci_dump_sqtds(sc,
+ xfer->td_transfer_first);
+ }
+#endif
+
+ EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_async_p_last);
+ }
+ if (methods == &ehci_device_intr_methods) {
+ EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ /*
+ * Only finish isochronous transfers once which will update
+ * "xfer->frlengths".
+ */
+ if (xfer->td_transfer_first &&
+ xfer->td_transfer_last) {
+ if (methods == &ehci_device_isoc_fs_methods) {
+ ehci_isoc_fs_done(sc, xfer);
+ }
+ if (methods == &ehci_device_isoc_hs_methods) {
+ ehci_isoc_hs_done(sc, xfer);
+ }
+ xfer->td_transfer_first = NULL;
+ xfer->td_transfer_last = NULL;
+ }
+ /* dequeue transfer and start next transfer */
+ usb2_transfer_done(xfer, error);
+}
+
+/*------------------------------------------------------------------------*
+ * ehci bulk support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_bulk_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_bulk_close(struct usb2_xfer *xfer)
+{
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_bulk_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_bulk_start(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ehci_setup_standard_chain(xfer, &sc->sc_async_p_last);
+
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ehci_device_bulk_methods =
+{
+ .open = ehci_device_bulk_open,
+ .close = ehci_device_bulk_close,
+ .enter = ehci_device_bulk_enter,
+ .start = ehci_device_bulk_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci control support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_ctrl_close(struct usb2_xfer *xfer)
+{
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_ctrl_start(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ehci_setup_standard_chain(xfer, &sc->sc_async_p_last);
+
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ehci_device_ctrl_methods =
+{
+ .open = ehci_device_ctrl_open,
+ .close = ehci_device_ctrl_close,
+ .enter = ehci_device_ctrl_enter,
+ .start = ehci_device_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_intr_open(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ uint16_t best;
+ uint16_t bit;
+ uint16_t x;
+ uint8_t slot;
+
+ /* Allocate a microframe slot first: */
+
+ slot = usb2_intr_schedule_adjust
+ (xfer->xroot->udev, xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX);
+
+ if (usb2_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) {
+ xfer->usb2_uframe = slot;
+ xfer->usb2_smask = (1 << slot) & 0xFF;
+ xfer->usb2_cmask = 0;
+ } else {
+ xfer->usb2_uframe = slot;
+ xfer->usb2_smask = (1 << slot) & 0x3F;
+ xfer->usb2_cmask = (-(4 << slot)) & 0xFE;
+ }
+
+ /*
+ * Find the best QH position corresponding to the given interval:
+ */
+
+ best = 0;
+ bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2;
+ while (bit) {
+ if (xfer->interval >= bit) {
+ x = bit;
+ best = bit;
+ while (x & bit) {
+ if (sc->sc_intr_stat[x] <
+ sc->sc_intr_stat[best]) {
+ best = x;
+ }
+ x++;
+ }
+ break;
+ }
+ bit >>= 1;
+ }
+
+ sc->sc_intr_stat[best]++;
+ xfer->qh_pos = best;
+
+ DPRINTFN(3, "best=%d interval=%d\n",
+ best, xfer->interval);
+}
+
+static void
+ehci_device_intr_close(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ uint8_t slot;
+
+ slot = usb2_intr_schedule_adjust
+ (xfer->xroot->udev, -(xfer->max_frame_size), xfer->usb2_uframe);
+
+ sc->sc_intr_stat[xfer->qh_pos]--;
+
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_intr_start(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ehci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]);
+
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ehci_device_intr_methods =
+{
+ .open = ehci_device_intr_open,
+ .close = ehci_device_intr_close,
+ .enter = ehci_device_intr_enter,
+ .start = ehci_device_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_isoc_fs_open(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_sitd_t *td;
+ uint32_t sitd_portaddr;
+ uint8_t ds;
+
+ sitd_portaddr =
+ EHCI_SITD_SET_ADDR(xfer->address) |
+ EHCI_SITD_SET_ENDPT(UE_GET_ADDR(xfer->endpoint)) |
+ EHCI_SITD_SET_HUBA(xfer->xroot->udev->hs_hub_addr) |
+ EHCI_SITD_SET_PORT(xfer->xroot->udev->hs_port_no);
+
+ if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) {
+ sitd_portaddr |= EHCI_SITD_SET_DIR_IN;
+ }
+ sitd_portaddr = htoehci32(sc, sitd_portaddr);
+
+ /* initialize all TD's */
+
+ for (ds = 0; ds != 2; ds++) {
+
+ for (td = xfer->td_start[ds]; td; td = td->obj_next) {
+
+ td->sitd_portaddr = sitd_portaddr;
+
+ /*
+ * TODO: make some kind of automatic
+ * SMASK/CMASK selection based on micro-frame
+ * usage
+ *
+ * micro-frame usage (8 microframes per 1ms)
+ */
+ td->sitd_back = htoehci32(sc, EHCI_LINK_TERMINATE);
+
+ usb2_pc_cpu_flush(td->page_cache);
+ }
+ }
+}
+
+static void
+ehci_device_isoc_fs_close(struct usb2_xfer *xfer)
+{
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_isoc_fs_enter(struct usb2_xfer *xfer)
+{
+ struct usb2_page_search buf_res;
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ struct usb2_fs_isoc_schedule *fss_start;
+ struct usb2_fs_isoc_schedule *fss_end;
+ struct usb2_fs_isoc_schedule *fss;
+ ehci_sitd_t *td;
+ ehci_sitd_t *td_last = NULL;
+ ehci_sitd_t **pp_last;
+ uint32_t *plen;
+ uint32_t buf_offset;
+ uint32_t nframes;
+ uint32_t temp;
+ uint32_t sitd_mask;
+ uint16_t tlen;
+ uint8_t sa;
+ uint8_t sb;
+ uint8_t error;
+
+#if USB_DEBUG
+ uint8_t once = 1;
+
+#endif
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->pipe->isoc_next, xfer->nframes);
+
+ /* get the current frame index */
+
+ nframes = EOREAD4(sc, EHCI_FRINDEX) / 8;
+
+ /*
+ * check if the frame index is within the window where the frames
+ * will be inserted
+ */
+ buf_offset = (nframes - xfer->pipe->isoc_next) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+
+ if ((xfer->pipe->is_synced == 0) ||
+ (buf_offset < 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->pipe->isoc_next = (nframes + 3) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ buf_offset = (xfer->pipe->isoc_next - nframes) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ usb2_fs_isoc_schedule_isoc_time_expand
+ (xfer->xroot->udev, &fss_start, &fss_end, nframes) + buf_offset +
+ xfer->nframes;
+
+ /* get the real number of frames */
+
+ nframes = xfer->nframes;
+
+ buf_offset = 0;
+
+ plen = xfer->frlengths;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+ xfer->td_transfer_first = td;
+
+ pp_last = &sc->sc_isoc_fs_p_last[xfer->pipe->isoc_next];
+
+ /* store starting position */
+
+ xfer->qh_pos = xfer->pipe->isoc_next;
+
+ fss = fss_start + (xfer->qh_pos % USB_ISOC_TIME_MAX);
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_fs_p_last[0];
+ }
+ if (fss >= fss_end) {
+ fss = fss_start;
+ }
+ /* reuse sitd_portaddr and sitd_back from last transfer */
+
+ if (*plen > xfer->max_frame_size) {
+#if USB_DEBUG
+ if (once) {
+ once = 0;
+ printf("%s: frame length(%d) exceeds %d "
+ "bytes (frame truncated)\n",
+ __FUNCTION__, *plen,
+ xfer->max_frame_size);
+ }
+#endif
+ *plen = xfer->max_frame_size;
+ }
+ /*
+ * We currently don't care if the ISOCHRONOUS schedule is
+ * full!
+ */
+ error = usb2_fs_isoc_schedule_alloc(fss, &sa, *plen);
+ if (error) {
+ /*
+ * The FULL speed schedule is FULL! Set length
+ * to zero.
+ */
+ *plen = 0;
+ }
+ if (*plen) {
+ /*
+ * only call "usb2_get_page()" when we have a
+ * non-zero length
+ */
+ usb2_get_page(xfer->frbuffers, buf_offset, &buf_res);
+ td->sitd_bp[0] = htoehci32(sc, buf_res.physaddr);
+ buf_offset += *plen;
+ /*
+ * NOTE: We need to subtract one from the offset so
+ * that we are on a valid page!
+ */
+ usb2_get_page(xfer->frbuffers, buf_offset - 1,
+ &buf_res);
+ temp = buf_res.physaddr & ~0xFFF;
+ } else {
+ td->sitd_bp[0] = 0;
+ temp = 0;
+ }
+
+ if (UE_GET_DIR(xfer->endpoint) == UE_DIR_OUT) {
+ tlen = *plen;
+ if (tlen <= 188) {
+ temp |= 1; /* T-count = 1, TP = ALL */
+ tlen = 1;
+ } else {
+ tlen += 187;
+ tlen /= 188;
+ temp |= tlen; /* T-count = [1..6] */
+ temp |= 8; /* TP = Begin */
+ }
+
+ tlen += sa;
+
+ if (tlen >= 8) {
+ sb = 0;
+ } else {
+ sb = (1 << tlen);
+ }
+
+ sa = (1 << sa);
+ sa = (sb - sa) & 0x3F;
+ sb = 0;
+ } else {
+ sb = (-(4 << sa)) & 0xFE;
+ sa = (1 << sa) & 0x3F;
+ }
+
+ sitd_mask = (EHCI_SITD_SET_SMASK(sa) |
+ EHCI_SITD_SET_CMASK(sb));
+
+ td->sitd_bp[1] = htoehci32(sc, temp);
+
+ td->sitd_mask = htoehci32(sc, sitd_mask);
+
+ if (nframes == 0) {
+ td->sitd_status = htole32
+ (EHCI_SITD_IOC |
+ EHCI_SITD_ACTIVE |
+ EHCI_SITD_SET_LEN(*plen));
+ } else {
+ td->sitd_status = htole32
+ (EHCI_SITD_ACTIVE |
+ EHCI_SITD_SET_LEN(*plen));
+ }
+ usb2_pc_cpu_flush(td->page_cache);
+
+#if USB_DEBUG
+ if (ehcidebug > 15) {
+ DPRINTF("FS-TD %d\n", nframes);
+ ehci_dump_sitd(sc, td);
+ }
+#endif
+ /* insert TD into schedule */
+ EHCI_APPEND_FS_TD(td, *pp_last);
+ pp_last++;
+
+ plen++;
+ fss++;
+ td_last = td;
+ td = td->obj_next;
+ }
+
+ xfer->td_transfer_last = td_last;
+
+ /* update isoc_next */
+ xfer->pipe->isoc_next = (pp_last - &sc->sc_isoc_fs_p_last[0]) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+}
+
+static void
+ehci_device_isoc_fs_start(struct usb2_xfer *xfer)
+{
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ehci_device_isoc_fs_methods =
+{
+ .open = ehci_device_isoc_fs_open,
+ .close = ehci_device_isoc_fs_close,
+ .enter = ehci_device_isoc_fs_enter,
+ .start = ehci_device_isoc_fs_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci high speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_isoc_hs_open(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_itd_t *td;
+ uint32_t temp;
+ uint8_t ds;
+
+ /* initialize all TD's */
+
+ for (ds = 0; ds != 2; ds++) {
+
+ for (td = xfer->td_start[ds]; td; td = td->obj_next) {
+
+ /* set TD inactive */
+ td->itd_status[0] = 0;
+ td->itd_status[1] = 0;
+ td->itd_status[2] = 0;
+ td->itd_status[3] = 0;
+ td->itd_status[4] = 0;
+ td->itd_status[5] = 0;
+ td->itd_status[6] = 0;
+ td->itd_status[7] = 0;
+
+ /* set endpoint and address */
+ td->itd_bp[0] = htole32
+ (EHCI_ITD_SET_ADDR(xfer->address) |
+ EHCI_ITD_SET_ENDPT(UE_GET_ADDR(xfer->endpoint)));
+
+ temp =
+ EHCI_ITD_SET_MPL(xfer->max_packet_size & 0x7FF);
+
+ /* set direction */
+ if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) {
+ temp |= EHCI_ITD_SET_DIR_IN;
+ }
+ /* set maximum packet size */
+ td->itd_bp[1] = htoehci32(sc, temp);
+
+ /* set transfer multiplier */
+ td->itd_bp[2] = htoehci32(sc, xfer->max_packet_count & 3);
+
+ usb2_pc_cpu_flush(td->page_cache);
+ }
+ }
+}
+
+static void
+ehci_device_isoc_hs_close(struct usb2_xfer *xfer)
+{
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_isoc_hs_enter(struct usb2_xfer *xfer)
+{
+ struct usb2_page_search buf_res;
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_itd_t *td;
+ ehci_itd_t *td_last = NULL;
+ ehci_itd_t **pp_last;
+ bus_size_t page_addr;
+ uint32_t *plen;
+ uint32_t status;
+ uint32_t buf_offset;
+ uint32_t nframes;
+ uint32_t itd_offset[8 + 1];
+ uint8_t x;
+ uint8_t td_no;
+ uint8_t page_no;
+
+#if USB_DEBUG
+ uint8_t once = 1;
+
+#endif
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->pipe->isoc_next, xfer->nframes);
+
+ /* get the current frame index */
+
+ nframes = EOREAD4(sc, EHCI_FRINDEX) / 8;
+
+ /*
+ * check if the frame index is within the window where the frames
+ * will be inserted
+ */
+ buf_offset = (nframes - xfer->pipe->isoc_next) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+
+ if ((xfer->pipe->is_synced == 0) ||
+ (buf_offset < ((xfer->nframes + 7) / 8))) {
+ /*
+ * 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->pipe->isoc_next = (nframes + 3) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ buf_offset = (xfer->pipe->isoc_next - nframes) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ usb2_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset +
+ ((xfer->nframes + 7) / 8);
+
+ /* get the real number of frames */
+
+ nframes = xfer->nframes;
+
+ buf_offset = 0;
+ td_no = 0;
+
+ plen = xfer->frlengths;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+ xfer->td_transfer_first = td;
+
+ pp_last = &sc->sc_isoc_hs_p_last[xfer->pipe->isoc_next];
+
+ /* store starting position */
+
+ xfer->qh_pos = xfer->pipe->isoc_next;
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_hs_p_last[0];
+ }
+ /* range check */
+ if (*plen > xfer->max_frame_size) {
+#if USB_DEBUG
+ if (once) {
+ once = 0;
+ printf("%s: frame length(%d) exceeds %d bytes "
+ "(frame truncated)\n",
+ __FUNCTION__, *plen, xfer->max_frame_size);
+ }
+#endif
+ *plen = xfer->max_frame_size;
+ }
+ status = (EHCI_ITD_SET_LEN(*plen) |
+ EHCI_ITD_ACTIVE |
+ EHCI_ITD_SET_PG(0));
+ td->itd_status[td_no] = htoehci32(sc, status);
+ itd_offset[td_no] = buf_offset;
+ buf_offset += *plen;
+ plen++;
+ td_no++;
+
+ if ((td_no == 8) || (nframes == 0)) {
+
+ /* the rest of the transfers are not active, if any */
+ for (x = td_no; x != 8; x++) {
+ td->itd_status[x] = 0; /* not active */
+ }
+
+ /* check if there is any data to be transferred */
+ if (itd_offset[0] != buf_offset) {
+ page_no = 0;
+ itd_offset[td_no] = buf_offset;
+
+ /* get first page offset */
+ usb2_get_page(xfer->frbuffers, itd_offset[0], &buf_res);
+ /* get page address */
+ page_addr = buf_res.physaddr & ~0xFFF;
+ /* update page address */
+ td->itd_bp[0] &= htoehci32(sc, 0xFFF);
+ td->itd_bp[0] |= htoehci32(sc, page_addr);
+
+ for (x = 0; x != td_no; x++) {
+ /* set page number and page offset */
+ status = (EHCI_ITD_SET_PG(page_no) |
+ (buf_res.physaddr & 0xFFF));
+ td->itd_status[x] |= htoehci32(sc, status);
+
+ /* get next page offset */
+ if (itd_offset[x + 1] == buf_offset) {
+ /*
+ * We subtract one so that
+ * we don't go off the last
+ * page!
+ */
+ usb2_get_page(xfer->frbuffers, buf_offset - 1, &buf_res);
+ } else {
+ usb2_get_page(xfer->frbuffers, itd_offset[x + 1], &buf_res);
+ }
+
+ /* check if we need a new page */
+ if ((buf_res.physaddr ^ page_addr) & ~0xFFF) {
+ /* new page needed */
+ page_addr = buf_res.physaddr & ~0xFFF;
+ if (page_no == 6) {
+ panic("%s: too many pages\n", __FUNCTION__);
+ }
+ page_no++;
+ /* update page address */
+ td->itd_bp[page_no] &= htoehci32(sc, 0xFFF);
+ td->itd_bp[page_no] |= htoehci32(sc, page_addr);
+ }
+ }
+ }
+ /* set IOC bit if we are complete */
+ if (nframes == 0) {
+ td->itd_status[7] |= htoehci32(sc, EHCI_ITD_IOC);
+ }
+ usb2_pc_cpu_flush(td->page_cache);
+#if USB_DEBUG
+ if (ehcidebug > 15) {
+ DPRINTF("HS-TD %d\n", nframes);
+ ehci_dump_itd(sc, td);
+ }
+#endif
+ /* insert TD into schedule */
+ EHCI_APPEND_HS_TD(td, *pp_last);
+ pp_last++;
+
+ td_no = 0;
+ td_last = td;
+ td = td->obj_next;
+ }
+ }
+
+ xfer->td_transfer_last = td_last;
+
+ /* update isoc_next */
+ xfer->pipe->isoc_next = (pp_last - &sc->sc_isoc_hs_p_last[0]) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+}
+
+static void
+ehci_device_isoc_hs_start(struct usb2_xfer *xfer)
+{
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ehci_device_isoc_hs_methods =
+{
+ .open = ehci_device_isoc_hs_open,
+ .close = ehci_device_isoc_hs_close,
+ .enter = ehci_device_isoc_hs_enter,
+ .start = ehci_device_isoc_hs_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci root control support
+ *------------------------------------------------------------------------*
+ * simulate a hardware hub by handling
+ * all the necessary requests
+ *------------------------------------------------------------------------*/
+
+static void
+ehci_root_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_root_ctrl_close(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_ctrl.xfer == xfer) {
+ sc->sc_root_ctrl.xfer = NULL;
+ }
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+/* data structures and routines
+ * to emulate the root hub:
+ */
+
+static const
+struct usb2_device_descriptor ehci_devd =
+{
+ sizeof(struct usb2_device_descriptor),
+ UDESC_DEVICE, /* type */
+ {0x00, 0x02}, /* USB version */
+ UDCLASS_HUB, /* class */
+ UDSUBCLASS_HUB, /* subclass */
+ UDPROTO_HSHUBSTT, /* protocol */
+ 64, /* max packet */
+ {0}, {0}, {0x00, 0x01}, /* device id */
+ 1, 2, 0, /* string indicies */
+ 1 /* # of configurations */
+};
+
+static const
+struct usb2_device_qualifier ehci_odevd =
+{
+ sizeof(struct usb2_device_qualifier),
+ UDESC_DEVICE_QUALIFIER, /* type */
+ {0x00, 0x02}, /* USB version */
+ UDCLASS_HUB, /* class */
+ UDSUBCLASS_HUB, /* subclass */
+ UDPROTO_FSHUB, /* protocol */
+ 0, /* max packet */
+ 0, /* # of configurations */
+ 0
+};
+
+static const struct ehci_config_desc ehci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb2_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(ehci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0 /* max power */
+ },
+
+ .ifcd = {
+ .bLength = sizeof(struct usb2_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = UIPROTO_HSHUBSTT,
+ 0
+ },
+
+ .endpd = {
+ .bLength = sizeof(struct usb2_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = UE_DIR_IN | EHCI_INTR_ENDPT,
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8, /* max packet (63 ports) */
+ .bInterval = 255,
+ },
+};
+
+static const
+struct usb2_hub_descriptor ehci_hubd =
+{
+ 0, /* dynamic length */
+ UDESC_HUB,
+ 0,
+ {0, 0},
+ 0,
+ 0,
+ {0},
+};
+
+static void
+ehci_disown(ehci_softc_t *sc, uint16_t index, uint8_t lowspeed)
+{
+ uint32_t port;
+ uint32_t v;
+
+ DPRINTF("index=%d lowspeed=%d\n", index, lowspeed);
+
+ port = EHCI_PORTSC(index);
+ v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR;
+ EOWRITE4(sc, port, v | EHCI_PS_PO);
+}
+
+static void
+ehci_root_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_root_ctrl_start(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ DPRINTF("\n");
+
+ sc->sc_root_ctrl.xfer = xfer;
+
+ usb2_bus_roothub_exec(xfer->xroot->bus);
+}
+
+static void
+ehci_root_ctrl_task(struct usb2_bus *bus)
+{
+ ehci_root_ctrl_poll(EHCI_BUS2SC(bus));
+}
+
+static void
+ehci_root_ctrl_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ char *ptr;
+ uint32_t port;
+ uint32_t v;
+ uint16_t i;
+ uint16_t value;
+ uint16_t index;
+ uint8_t l;
+ uint8_t use_polling;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_SETUP) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ ehci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* buffer reset */
+ std->ptr = sc->sc_hub_desc.temp;
+ std->len = 0;
+
+ value = UGETW(std->req.wValue);
+ index = UGETW(std->req.wIndex);
+
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x "
+ "wValue=0x%04x wIndex=0x%04x\n",
+ std->req.bmRequestType, std->req.bRequest,
+ UGETW(std->req.wLength), value, index);
+
+#define C(x,y) ((x) | ((y) << 8))
+ switch (C(std->req.bRequest, std->req.bmRequestType)) {
+ case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
+ /*
+ * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops
+ * for the integrated root hub.
+ */
+ break;
+ case C(UR_GET_CONFIG, UT_READ_DEVICE):
+ std->len = 1;
+ sc->sc_hub_desc.temp[0] = sc->sc_conf;
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ std->len = sizeof(ehci_devd);
+ sc->sc_hub_desc.devd = ehci_devd;
+ break;
+ /*
+ * We can't really operate at another speed,
+ * but the specification says we need this
+ * descriptor:
+ */
+ case UDESC_DEVICE_QUALIFIER:
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ std->len = sizeof(ehci_odevd);
+ sc->sc_hub_desc.odevd = ehci_odevd;
+ break;
+
+ case UDESC_CONFIG:
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ std->len = sizeof(ehci_confd);
+ std->ptr = USB_ADD_BYTES(&ehci_confd, 0);
+ break;
+
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ ptr = "\001";
+ break;
+
+ case 1: /* Vendor */
+ ptr = sc->sc_vendor;
+ break;
+
+ case 2: /* Product */
+ ptr = "EHCI root HUB";
+ break;
+
+ default:
+ ptr = "";
+ break;
+ }
+
+ std->len = usb2_make_str_desc
+ (sc->sc_hub_desc.temp,
+ sizeof(sc->sc_hub_desc.temp),
+ ptr);
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_GET_INTERFACE, UT_READ_INTERFACE):
+ std->len = 1;
+ sc->sc_hub_desc.temp[0] = 0;
+ break;
+ case C(UR_GET_STATUS, UT_READ_DEVICE):
+ std->len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED);
+ break;
+ case C(UR_GET_STATUS, UT_READ_INTERFACE):
+ case C(UR_GET_STATUS, UT_READ_ENDPOINT):
+ std->len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, 0);
+ break;
+ case C(UR_SET_ADDRESS, UT_WRITE_DEVICE):
+ if (value >= USB_MAX_DEVICES) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_addr = value;
+ break;
+ case C(UR_SET_CONFIG, UT_WRITE_DEVICE):
+ if ((value != 0) && (value != 1)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_conf = value;
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_SET_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT):
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
+ break;
+ case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT):
+ break;
+ /* Hub requests */
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER):
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE\n");
+
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = EHCI_PORTSC(index);
+ v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR;
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ EOWRITE4(sc, port, v & ~EHCI_PS_PE);
+ break;
+ case UHF_PORT_SUSPEND:
+ if ((v & EHCI_PS_SUSP) && (!(v & EHCI_PS_FPR))) {
+
+ /*
+ * waking up a High Speed device is rather
+ * complicated if
+ */
+ EOWRITE4(sc, port, v | EHCI_PS_FPR);
+ }
+ /* wait 20ms for resume sequence to complete */
+ if (use_polling) {
+ /* polling */
+ DELAY(20000);
+ } else {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50);
+ }
+
+ EOWRITE4(sc, port, v & ~(EHCI_PS_SUSP |
+ EHCI_PS_FPR | (3 << 10) /* High Speed */ ));
+
+ /* settle time */
+ if (use_polling) {
+ /* polling */
+ DELAY(4000);
+ } else {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250);
+ }
+ break;
+ case UHF_PORT_POWER:
+ EOWRITE4(sc, port, v & ~EHCI_PS_PP);
+ break;
+ case UHF_PORT_TEST:
+ DPRINTFN(3, "clear port test "
+ "%d\n", index);
+ break;
+ case UHF_PORT_INDICATOR:
+ DPRINTFN(3, "clear port ind "
+ "%d\n", index);
+ EOWRITE4(sc, port, v & ~EHCI_PS_PIC);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ EOWRITE4(sc, port, v | EHCI_PS_CSC);
+ break;
+ case UHF_C_PORT_ENABLE:
+ EOWRITE4(sc, port, v | EHCI_PS_PEC);
+ break;
+ case UHF_C_PORT_SUSPEND:
+ EOWRITE4(sc, port, v | EHCI_PS_SUSP);
+ break;
+ case UHF_C_PORT_OVER_CURRENT:
+ EOWRITE4(sc, port, v | EHCI_PS_OCC);
+ break;
+ case UHF_C_PORT_RESET:
+ sc->sc_isreset = 0;
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ v = EOREAD4(sc, EHCI_HCSPARAMS);
+
+ sc->sc_hub_desc.hubd = ehci_hubd;
+ sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport;
+ USETW(sc->sc_hub_desc.hubd.wHubCharacteristics,
+ (EHCI_HCS_PPC(v) ? UHD_PWR_INDIVIDUAL : UHD_PWR_NO_SWITCH) |
+ (EHCI_HCS_P_INDICATOR(EREAD4(sc, EHCI_HCSPARAMS)) ?
+ UHD_PORT_IND : 0));
+ /* XXX can't find out? */
+ sc->sc_hub_desc.hubd.bPwrOn2PwrGood = 200;
+ for (l = 0; l < sc->sc_noport; l++) {
+ /* XXX can't find out? */
+ sc->sc_hub_desc.hubd.DeviceRemovable[l / 8] &= ~(1 << (l % 8));
+ }
+ sc->sc_hub_desc.hubd.bDescLength =
+ 8 + ((sc->sc_noport + 7) / 8);
+ std->len = sc->sc_hub_desc.hubd.bDescLength;
+ break;
+ case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE):
+ std->len = 16;
+ bzero(sc->sc_hub_desc.temp, 16);
+ break;
+ case C(UR_GET_STATUS, UT_READ_CLASS_OTHER):
+ DPRINTFN(9, "get port status i=%d\n",
+ index);
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ v = EOREAD4(sc, EHCI_PORTSC(index));
+ DPRINTFN(9, "port status=0x%04x\n", v);
+ if (sc->sc_flags & EHCI_SCFLG_FORCESPEED) {
+ if ((v & 0xc000000) == 0x8000000)
+ i = UPS_HIGH_SPEED;
+ else if ((v & 0xc000000) == 0x4000000)
+ i = UPS_LOW_SPEED;
+ else
+ i = 0;
+ } else {
+ i = UPS_HIGH_SPEED;
+ }
+ if (v & EHCI_PS_CS)
+ i |= UPS_CURRENT_CONNECT_STATUS;
+ if (v & EHCI_PS_PE)
+ i |= UPS_PORT_ENABLED;
+ if ((v & EHCI_PS_SUSP) && !(v & EHCI_PS_FPR))
+ i |= UPS_SUSPEND;
+ if (v & EHCI_PS_OCA)
+ i |= UPS_OVERCURRENT_INDICATOR;
+ if (v & EHCI_PS_PR)
+ i |= UPS_RESET;
+ if (v & EHCI_PS_PP)
+ i |= UPS_PORT_POWER;
+ USETW(sc->sc_hub_desc.ps.wPortStatus, i);
+ i = 0;
+ if (v & EHCI_PS_CSC)
+ i |= UPS_C_CONNECT_STATUS;
+ if (v & EHCI_PS_PEC)
+ i |= UPS_C_PORT_ENABLED;
+ if (v & EHCI_PS_OCC)
+ i |= UPS_C_OVERCURRENT_INDICATOR;
+ if (v & EHCI_PS_FPR)
+ i |= UPS_C_SUSPEND;
+ if (sc->sc_isreset)
+ i |= UPS_C_PORT_RESET;
+ USETW(sc->sc_hub_desc.ps.wPortChange, i);
+ std->len = sizeof(sc->sc_hub_desc.ps);
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER):
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = EHCI_PORTSC(index);
+ v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR;
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ EOWRITE4(sc, port, v | EHCI_PS_PE);
+ break;
+ case UHF_PORT_SUSPEND:
+ EOWRITE4(sc, port, v | EHCI_PS_SUSP);
+ break;
+ case UHF_PORT_RESET:
+ DPRINTFN(6, "reset port %d\n", index);
+#if USB_DEBUG
+ if (ehcinohighspeed) {
+ /*
+ * Connect USB device to companion
+ * controller.
+ */
+ ehci_disown(sc, index, 1);
+ break;
+ }
+#endif
+ if (EHCI_PS_IS_LOWSPEED(v)) {
+ /* Low speed device, give up ownership. */
+ ehci_disown(sc, index, 1);
+ break;
+ }
+ /* Start reset sequence. */
+ v &= ~(EHCI_PS_PE | EHCI_PS_PR);
+ EOWRITE4(sc, port, v | EHCI_PS_PR);
+
+ if (use_polling) {
+ /* polling */
+ DELAY(USB_PORT_ROOT_RESET_DELAY * 1000);
+ } else {
+ /* Wait for reset to complete. */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_PORT_ROOT_RESET_DELAY));
+ }
+
+ /* Terminate reset sequence. */
+ if (!(sc->sc_flags & EHCI_SCFLG_NORESTERM))
+ EOWRITE4(sc, port, v);
+
+ if (use_polling) {
+ /* polling */
+ DELAY(EHCI_PORT_RESET_COMPLETE * 1000);
+ } else {
+ /* Wait for HC to complete reset. */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(EHCI_PORT_RESET_COMPLETE));
+ }
+
+ v = EOREAD4(sc, port);
+ DPRINTF("ehci after reset, status=0x%08x\n", v);
+ if (v & EHCI_PS_PR) {
+ device_printf(sc->sc_bus.bdev,
+ "port reset timeout\n");
+ std->err = USB_ERR_TIMEOUT;
+ goto done;
+ }
+ if (!(v & EHCI_PS_PE)) {
+ /*
+ * Not a high speed device, give up
+ * ownership.
+ */
+ ehci_disown(sc, index, 0);
+ break;
+ }
+ sc->sc_isreset = 1;
+ DPRINTF("ehci port %d reset, status = 0x%08x\n",
+ index, v);
+ break;
+
+ case UHF_PORT_POWER:
+ DPRINTFN(3, "set port power %d\n", index);
+ EOWRITE4(sc, port, v | EHCI_PS_PP);
+ break;
+
+ case UHF_PORT_TEST:
+ DPRINTFN(3, "set port test %d\n", index);
+ break;
+
+ case UHF_PORT_INDICATOR:
+ DPRINTFN(3, "set port ind %d\n", index);
+ EOWRITE4(sc, port, v | EHCI_PS_PIC);
+ break;
+
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER):
+ case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER):
+ case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER):
+ case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER):
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+done:
+ return;
+}
+
+static void
+ehci_root_ctrl_poll(ehci_softc_t *sc)
+{
+ usb2_sw_transfer(&sc->sc_root_ctrl,
+ &ehci_root_ctrl_done);
+}
+
+struct usb2_pipe_methods ehci_root_ctrl_methods =
+{
+ .open = ehci_root_ctrl_open,
+ .close = ehci_root_ctrl_close,
+ .enter = ehci_root_ctrl_enter,
+ .start = ehci_root_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 0,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci root interrupt support
+ *------------------------------------------------------------------------*/
+static void
+ehci_root_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_root_intr_close(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_intr.xfer == xfer) {
+ sc->sc_root_intr.xfer = NULL;
+ }
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_root_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_root_intr_start(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_intr.xfer = xfer;
+}
+
+struct usb2_pipe_methods ehci_root_intr_methods =
+{
+ .open = ehci_root_intr_open,
+ .close = ehci_root_intr_close,
+ .enter = ehci_root_intr_enter,
+ .start = ehci_root_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+static void
+ehci_xfer_setup(struct usb2_setup_params *parm)
+{
+ struct usb2_page_search page_info;
+ struct usb2_page_cache *pc;
+ ehci_softc_t *sc;
+ struct usb2_xfer *xfer;
+ void *last_obj;
+ uint32_t nqtd;
+ uint32_t nqh;
+ uint32_t nsitd;
+ uint32_t nitd;
+ uint32_t n;
+
+ sc = EHCI_BUS2SC(parm->udev->bus);
+ xfer = parm->curr_xfer;
+
+ nqtd = 0;
+ nqh = 0;
+ nsitd = 0;
+ nitd = 0;
+
+ /*
+ * compute maximum number of some structures
+ */
+ if (parm->methods == &ehci_device_ctrl_methods) {
+
+ /*
+ * The proof for the "nqtd" formula is illustrated like
+ * this:
+ *
+ * +------------------------------------+
+ * | |
+ * | |remainder -> |
+ * | +-----+---+ |
+ * | | xxx | x | frm 0 |
+ * | +-----+---++ |
+ * | | xxx | xx | frm 1 |
+ * | +-----+----+ |
+ * | ... |
+ * +------------------------------------+
+ *
+ * "xxx" means a completely full USB transfer descriptor
+ *
+ * "x" and "xx" means a short USB packet
+ *
+ * For the remainder of an USB transfer modulo
+ * "max_data_length" we need two USB transfer descriptors.
+ * One to transfer the remaining data and one to finalise
+ * with a zero length packet in case the "force_short_xfer"
+ * flag is set. We only need two USB transfer descriptors in
+ * the case where the transfer length of the first one is a
+ * factor of "max_frame_size". The rest of the needed USB
+ * transfer descriptors is given by the buffer size divided
+ * by the maximum data payload.
+ */
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX;
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nqh = 1;
+ nqtd = ((2 * xfer->nframes) + 1 /* STATUS */
+ + (xfer->max_data_length / xfer->max_usb2_frame_size));
+
+ } else if (parm->methods == &ehci_device_bulk_methods) {
+
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX;
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nqh = 1;
+ nqtd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_usb2_frame_size));
+
+ } else if (parm->methods == &ehci_device_intr_methods) {
+
+ if (parm->speed == USB_SPEED_HIGH) {
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 3;
+ } else if (parm->speed == USB_SPEED_FULL) {
+ parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME;
+ parm->hc_max_packet_count = 1;
+ } else {
+ parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME / 8;
+ parm->hc_max_packet_count = 1;
+ }
+
+ parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX;
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nqh = 1;
+ nqtd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_usb2_frame_size));
+
+ } else if (parm->methods == &ehci_device_isoc_fs_methods) {
+
+ parm->hc_max_packet_size = 0x3FF;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x3FF;
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nsitd = xfer->nframes;
+
+ } else if (parm->methods == &ehci_device_isoc_hs_methods) {
+
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 3;
+ parm->hc_max_frame_size = 0xC00;
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nitd = (xfer->nframes + 7) / 8;
+
+ } else {
+
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x400;
+
+ usb2_transfer_setup_sub(parm);
+ }
+
+alloc_dma_set:
+
+ if (parm->err) {
+ return;
+ }
+ /*
+ * Allocate queue heads and transfer descriptors
+ */
+ last_obj = NULL;
+
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ehci_itd_t),
+ EHCI_ITD_ALIGN, nitd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nitd; n++) {
+ ehci_itd_t *td;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ td = page_info.buffer;
+
+ /* init TD */
+ td->itd_self = htoehci32(sc, page_info.physaddr | EHCI_LINK_ITD);
+ td->obj_next = last_obj;
+ td->page_cache = pc + n;
+
+ last_obj = td;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ehci_sitd_t),
+ EHCI_SITD_ALIGN, nsitd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nsitd; n++) {
+ ehci_sitd_t *td;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ td = page_info.buffer;
+
+ /* init TD */
+ td->sitd_self = htoehci32(sc, page_info.physaddr | EHCI_LINK_SITD);
+ td->obj_next = last_obj;
+ td->page_cache = pc + n;
+
+ last_obj = td;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ehci_qtd_t),
+ EHCI_QTD_ALIGN, nqtd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nqtd; n++) {
+ ehci_qtd_t *qtd;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ qtd = page_info.buffer;
+
+ /* init TD */
+ qtd->qtd_self = htoehci32(sc, page_info.physaddr);
+ qtd->obj_next = last_obj;
+ qtd->page_cache = pc + n;
+
+ last_obj = qtd;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ last_obj = NULL;
+
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ehci_qh_t),
+ EHCI_QH_ALIGN, nqh)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nqh; n++) {
+ ehci_qh_t *qh;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ qh = page_info.buffer;
+
+ /* init QH */
+ qh->qh_self = htoehci32(sc, page_info.physaddr | EHCI_LINK_QH);
+ qh->obj_next = last_obj;
+ qh->page_cache = pc + n;
+
+ last_obj = qh;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ if (!xfer->flags_int.curr_dma_set) {
+ xfer->flags_int.curr_dma_set = 1;
+ goto alloc_dma_set;
+ }
+}
+
+static void
+ehci_xfer_unsetup(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc,
+ struct usb2_pipe *pipe)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ pipe, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb2_mode,
+ sc->sc_addr);
+
+ if (udev->flags.usb2_mode != USB_MODE_HOST) {
+ /* not supported */
+ return;
+ }
+ if (udev->device_index == sc->sc_addr) {
+ switch (edesc->bEndpointAddress) {
+ case USB_CONTROL_ENDPOINT:
+ pipe->methods = &ehci_root_ctrl_methods;
+ break;
+ case UE_DIR_IN | EHCI_INTR_ENDPT:
+ pipe->methods = &ehci_root_intr_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ } else {
+ if ((udev->speed != USB_SPEED_HIGH) &&
+ ((udev->hs_hub_addr == 0) ||
+ (udev->hs_port_no == 0) ||
+ (udev->bus->devices[udev->hs_hub_addr] == NULL) ||
+ (udev->bus->devices[udev->hs_hub_addr]->hub == NULL))) {
+ /* We need a transaction translator */
+ goto done;
+ }
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ pipe->methods = &ehci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ pipe->methods = &ehci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ if (udev->speed == USB_SPEED_HIGH) {
+ pipe->methods = &ehci_device_isoc_hs_methods;
+ } else if (udev->speed == USB_SPEED_FULL) {
+ pipe->methods = &ehci_device_isoc_fs_methods;
+ }
+ break;
+ case UE_BULK:
+ if (udev->speed != USB_SPEED_LOW) {
+ pipe->methods = &ehci_device_bulk_methods;
+ }
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+done:
+ return;
+}
+
+static void
+ehci_get_dma_delay(struct usb2_bus *bus, uint32_t *pus)
+{
+ /*
+ * Wait until the hardware has finished any possible use of
+ * the transfer descriptor(s) and QH
+ */
+ *pus = (188); /* microseconds */
+}
+
+static void
+ehci_device_resume(struct usb2_device *udev)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
+ struct usb2_xfer *xfer;
+ struct usb2_pipe_methods *methods;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+
+ if (xfer->xroot->udev == udev) {
+
+ methods = xfer->pipe->methods;
+
+ if ((methods == &ehci_device_bulk_methods) ||
+ (methods == &ehci_device_ctrl_methods)) {
+ EHCI_APPEND_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_async_p_last);
+ }
+ if (methods == &ehci_device_intr_methods) {
+ EHCI_APPEND_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+ehci_device_suspend(struct usb2_device *udev)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
+ struct usb2_xfer *xfer;
+ struct usb2_pipe_methods *methods;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+
+ if (xfer->xroot->udev == udev) {
+
+ methods = xfer->pipe->methods;
+
+ if ((methods == &ehci_device_bulk_methods) ||
+ (methods == &ehci_device_ctrl_methods)) {
+ EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_async_p_last);
+ }
+ if (methods == &ehci_device_intr_methods) {
+ EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+ehci_set_hw_power(struct usb2_bus *bus)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(bus);
+ uint32_t temp;
+ uint32_t flags;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(bus);
+
+ flags = bus->hw_power_state;
+
+ temp = EOREAD4(sc, EHCI_USBCMD);
+
+ temp &= ~(EHCI_CMD_ASE | EHCI_CMD_PSE);
+
+ if (flags & (USB_HW_POWER_CONTROL |
+ USB_HW_POWER_BULK)) {
+ DPRINTF("Async is active\n");
+ temp |= EHCI_CMD_ASE;
+ }
+ if (flags & (USB_HW_POWER_INTERRUPT |
+ USB_HW_POWER_ISOC)) {
+ DPRINTF("Periodic is active\n");
+ temp |= EHCI_CMD_PSE;
+ }
+ EOWRITE4(sc, EHCI_USBCMD, temp);
+
+ USB_BUS_UNLOCK(bus);
+
+ return;
+}
+
+struct usb2_bus_methods ehci_bus_methods =
+{
+ .pipe_init = ehci_pipe_init,
+ .xfer_setup = ehci_xfer_setup,
+ .xfer_unsetup = ehci_xfer_unsetup,
+ .do_poll = ehci_do_poll,
+ .get_dma_delay = ehci_get_dma_delay,
+ .device_resume = ehci_device_resume,
+ .device_suspend = ehci_device_suspend,
+ .set_hw_power = ehci_set_hw_power,
+ .roothub_exec = ehci_root_ctrl_task,
+};
diff --git a/sys/dev/usb/controller/ehci.h b/sys/dev/usb/controller/ehci.h
new file mode 100644
index 000000000000..9d7baa1eb3e6
--- /dev/null
+++ b/sys/dev/usb/controller/ehci.h
@@ -0,0 +1,532 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2001 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (lennart@augustsson.net).
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef _EHCI_H_
+#define _EHCI_H_
+
+#define EHCI_MAX_DEVICES USB_MAX_DEVICES
+
+/* PCI config registers */
+#define PCI_CBMEM 0x10 /* configuration base MEM */
+#define PCI_INTERFACE_EHCI 0x20
+#define PCI_USBREV 0x60 /* RO USB protocol revision */
+#define PCI_USB_REV_MASK 0xff
+#define PCI_USB_REV_PRE_1_0 0x00
+#define PCI_USB_REV_1_0 0x10
+#define PCI_USB_REV_1_1 0x11
+#define PCI_USB_REV_2_0 0x20
+#define PCI_EHCI_FLADJ 0x61 /* RW Frame len adj, SOF=59488+6*fladj */
+#define PCI_EHCI_PORTWAKECAP 0x62 /* RW Port wake caps (opt) */
+
+/* EHCI Extended Capabilities */
+#define EHCI_EC_LEGSUP 0x01
+#define EHCI_EECP_NEXT(x) (((x) >> 8) & 0xff)
+#define EHCI_EECP_ID(x) ((x) & 0xff)
+
+/* Legacy support extended capability */
+#define EHCI_LEGSUP_BIOS_SEM 0x02
+#define EHCI_LEGSUP_OS_SEM 0x03
+#define EHCI_LEGSUP_USBLEGCTLSTS 0x04
+
+/* EHCI capability registers */
+#define EHCI_CAPLENGTH 0x00 /* RO Capability register length field */
+/* reserved 0x01 */
+#define EHCI_HCIVERSION 0x02 /* RO Interface version number */
+#define EHCI_HCSPARAMS 0x04 /* RO Structural parameters */
+#define EHCI_HCS_DEBUGPORT(x) (((x) >> 20) & 0xf)
+#define EHCI_HCS_P_INDICATOR(x) ((x) & 0x10000)
+#define EHCI_HCS_N_CC(x) (((x) >> 12) & 0xf) /* # of companion ctlrs */
+#define EHCI_HCS_N_PCC(x) (((x) >> 8) & 0xf) /* # of ports per comp. */
+#define EHCI_HCS_PPC(x) ((x) & 0x10) /* port power control */
+#define EHCI_HCS_N_PORTS(x) ((x) & 0xf) /* # of ports */
+#define EHCI_HCCPARAMS 0x08 /* RO Capability parameters */
+#define EHCI_HCC_EECP(x) (((x) >> 8) & 0xff) /* extended ports caps */
+#define EHCI_HCC_IST(x) (((x) >> 4) & 0xf) /* isoc sched threshold */
+#define EHCI_HCC_ASPC(x) ((x) & 0x4) /* async sched park cap */
+#define EHCI_HCC_PFLF(x) ((x) & 0x2) /* prog frame list flag */
+#define EHCI_HCC_64BIT(x) ((x) & 0x1) /* 64 bit address cap */
+#define EHCI_HCSP_PORTROUTE 0x0c /* RO Companion port route description */
+
+/* EHCI operational registers. Offset given by EHCI_CAPLENGTH register */
+#define EHCI_USBCMD 0x00 /* RO, RW, WO Command register */
+#define EHCI_CMD_ITC_M 0x00ff0000 /* RW interrupt threshold ctrl */
+#define EHCI_CMD_ITC_1 0x00010000
+#define EHCI_CMD_ITC_2 0x00020000
+#define EHCI_CMD_ITC_4 0x00040000
+#define EHCI_CMD_ITC_8 0x00080000
+#define EHCI_CMD_ITC_16 0x00100000
+#define EHCI_CMD_ITC_32 0x00200000
+#define EHCI_CMD_ITC_64 0x00400000
+#define EHCI_CMD_ASPME 0x00000800 /* RW/RO async park enable */
+#define EHCI_CMD_ASPMC 0x00000300 /* RW/RO async park count */
+#define EHCI_CMD_LHCR 0x00000080 /* RW light host ctrl reset */
+#define EHCI_CMD_IAAD 0x00000040 /* RW intr on async adv door
+ * bell */
+#define EHCI_CMD_ASE 0x00000020 /* RW async sched enable */
+#define EHCI_CMD_PSE 0x00000010 /* RW periodic sched enable */
+#define EHCI_CMD_FLS_M 0x0000000c /* RW/RO frame list size */
+#define EHCI_CMD_FLS(x) (((x) >> 2) & 3) /* RW/RO frame list size */
+#define EHCI_CMD_HCRESET 0x00000002 /* RW reset */
+#define EHCI_CMD_RS 0x00000001 /* RW run/stop */
+#define EHCI_USBSTS 0x04 /* RO, RW, RWC Status register */
+#define EHCI_STS_ASS 0x00008000 /* RO async sched status */
+#define EHCI_STS_PSS 0x00004000 /* RO periodic sched status */
+#define EHCI_STS_REC 0x00002000 /* RO reclamation */
+#define EHCI_STS_HCH 0x00001000 /* RO host controller halted */
+#define EHCI_STS_IAA 0x00000020 /* RWC interrupt on async adv */
+#define EHCI_STS_HSE 0x00000010 /* RWC host system error */
+#define EHCI_STS_FLR 0x00000008 /* RWC frame list rollover */
+#define EHCI_STS_PCD 0x00000004 /* RWC port change detect */
+#define EHCI_STS_ERRINT 0x00000002 /* RWC error interrupt */
+#define EHCI_STS_INT 0x00000001 /* RWC interrupt */
+#define EHCI_STS_INTRS(x) ((x) & 0x3f)
+
+/*
+ * NOTE: the doorbell interrupt is enabled, but the doorbell is never
+ * used! SiS chipsets require this.
+ */
+#define EHCI_NORMAL_INTRS (EHCI_STS_IAA | EHCI_STS_HSE | \
+ EHCI_STS_PCD | EHCI_STS_ERRINT | EHCI_STS_INT)
+
+#define EHCI_USBINTR 0x08 /* RW Interrupt register */
+#define EHCI_INTR_IAAE 0x00000020 /* interrupt on async advance
+ * ena */
+#define EHCI_INTR_HSEE 0x00000010 /* host system error ena */
+#define EHCI_INTR_FLRE 0x00000008 /* frame list rollover ena */
+#define EHCI_INTR_PCIE 0x00000004 /* port change ena */
+#define EHCI_INTR_UEIE 0x00000002 /* USB error intr ena */
+#define EHCI_INTR_UIE 0x00000001 /* USB intr ena */
+
+#define EHCI_FRINDEX 0x0c /* RW Frame Index register */
+
+#define EHCI_CTRLDSSEGMENT 0x10 /* RW Control Data Structure Segment */
+
+#define EHCI_PERIODICLISTBASE 0x14 /* RW Periodic List Base */
+#define EHCI_ASYNCLISTADDR 0x18 /* RW Async List Base */
+
+#define EHCI_CONFIGFLAG 0x40 /* RW Configure Flag register */
+#define EHCI_CONF_CF 0x00000001 /* RW configure flag */
+
+#define EHCI_PORTSC(n) (0x40+(4*(n))) /* RO, RW, RWC Port Status reg */
+#define EHCI_PS_WKOC_E 0x00400000 /* RW wake on over current ena */
+#define EHCI_PS_WKDSCNNT_E 0x00200000 /* RW wake on disconnect ena */
+#define EHCI_PS_WKCNNT_E 0x00100000 /* RW wake on connect ena */
+#define EHCI_PS_PTC 0x000f0000 /* RW port test control */
+#define EHCI_PS_PIC 0x0000c000 /* RW port indicator control */
+#define EHCI_PS_PO 0x00002000 /* RW port owner */
+#define EHCI_PS_PP 0x00001000 /* RW,RO port power */
+#define EHCI_PS_LS 0x00000c00 /* RO line status */
+#define EHCI_PS_IS_LOWSPEED(x) (((x) & EHCI_PS_LS) == 0x00000400)
+#define EHCI_PS_PR 0x00000100 /* RW port reset */
+#define EHCI_PS_SUSP 0x00000080 /* RW suspend */
+#define EHCI_PS_FPR 0x00000040 /* RW force port resume */
+#define EHCI_PS_OCC 0x00000020 /* RWC over current change */
+#define EHCI_PS_OCA 0x00000010 /* RO over current active */
+#define EHCI_PS_PEC 0x00000008 /* RWC port enable change */
+#define EHCI_PS_PE 0x00000004 /* RW port enable */
+#define EHCI_PS_CSC 0x00000002 /* RWC connect status change */
+#define EHCI_PS_CS 0x00000001 /* RO connect status */
+#define EHCI_PS_CLEAR (EHCI_PS_OCC | EHCI_PS_PEC | EHCI_PS_CSC)
+
+#define EHCI_USBMODE 0x68 /* RW USB Device mode register */
+#define EHCI_UM_CM 0x00000003 /* R/WO Controller Mode */
+#define EHCI_UM_CM_IDLE 0x0 /* Idle */
+#define EHCI_UM_CM_HOST 0x3 /* Host Controller */
+#define EHCI_UM_ES 0x00000004 /* R/WO Endian Select */
+#define EHCI_UM_ES_LE 0x0 /* Little-endian byte alignment */
+#define EHCI_UM_ES_BE 0x4 /* Big-endian byte alignment */
+#define EHCI_UM_SDIS 0x00000010 /* R/WO Stream Disable Mode */
+
+#define EHCI_PORT_RESET_COMPLETE 2 /* ms */
+
+/*
+ * Alignment NOTE: structures must be aligned so that the hardware can index
+ * without performing addition.
+ */
+#define EHCI_FRAMELIST_ALIGN 0x1000 /* bytes */
+#define EHCI_FRAMELIST_COUNT 1024 /* units */
+#define EHCI_VIRTUAL_FRAMELIST_COUNT 128 /* units */
+
+#if ((8*EHCI_VIRTUAL_FRAMELIST_COUNT) < USB_MAX_HS_ISOC_FRAMES_PER_XFER)
+#error "maximum number of high-speed isochronous frames is higher than supported!"
+#endif
+
+#if (EHCI_VIRTUAL_FRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER)
+#error "maximum number of full-speed isochronous frames is higher than supported!"
+#endif
+
+/* Link types */
+#define EHCI_LINK_TERMINATE 0x00000001
+#define EHCI_LINK_TYPE(x) ((x) & 0x00000006)
+#define EHCI_LINK_ITD 0x0
+#define EHCI_LINK_QH 0x2
+#define EHCI_LINK_SITD 0x4
+#define EHCI_LINK_FSTN 0x6
+#define EHCI_LINK_ADDR(x) ((x) &~ 0x1f)
+
+/* Structures alignment (bytes) */
+#define EHCI_ITD_ALIGN 128
+#define EHCI_SITD_ALIGN 64
+#define EHCI_QTD_ALIGN 64
+#define EHCI_QH_ALIGN 128
+#define EHCI_FSTN_ALIGN 32
+/* Data buffers are divided into one or more pages */
+#define EHCI_PAGE_SIZE 0x1000
+#if ((USB_PAGE_SIZE < EHCI_PAGE_SIZE) || (EHCI_PAGE_SIZE == 0) || \
+ (USB_PAGE_SIZE < EHCI_ITD_ALIGN) || (EHCI_ITD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < EHCI_SITD_ALIGN) || (EHCI_SITD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < EHCI_QTD_ALIGN) || (EHCI_QTD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < EHCI_QH_ALIGN) || (EHCI_QH_ALIGN == 0) || \
+ (USB_PAGE_SIZE < EHCI_FSTN_ALIGN) || (EHCI_FSTN_ALIGN == 0))
+#error "Invalid USB page size!"
+#endif
+
+
+/*
+ * Isochronous Transfer Descriptor. This descriptor is used for high speed
+ * transfers only.
+ */
+struct ehci_itd {
+ volatile uint32_t itd_next;
+ volatile uint32_t itd_status[8];
+#define EHCI_ITD_SET_LEN(x) ((x) << 16)
+#define EHCI_ITD_GET_LEN(x) (((x) >> 16) & 0xFFF)
+#define EHCI_ITD_IOC (1 << 15)
+#define EHCI_ITD_SET_PG(x) ((x) << 12)
+#define EHCI_ITD_GET_PG(x) (((x) >> 12) & 0x7)
+#define EHCI_ITD_SET_OFFS(x) (x)
+#define EHCI_ITD_GET_OFFS(x) (((x) >> 0) & 0xFFF)
+#define EHCI_ITD_ACTIVE (1 << 31)
+#define EHCI_ITD_DATABUFERR (1 << 30)
+#define EHCI_ITD_BABBLE (1 << 29)
+#define EHCI_ITD_XACTERR (1 << 28)
+ volatile uint32_t itd_bp[7];
+ /* itd_bp[0] */
+#define EHCI_ITD_SET_ADDR(x) (x)
+#define EHCI_ITD_GET_ADDR(x) (((x) >> 0) & 0x7F)
+#define EHCI_ITD_SET_ENDPT(x) ((x) << 8)
+#define EHCI_ITD_GET_ENDPT(x) (((x) >> 8) & 0xF)
+ /* itd_bp[1] */
+#define EHCI_ITD_SET_DIR_IN (1 << 11)
+#define EHCI_ITD_SET_DIR_OUT (0 << 11)
+#define EHCI_ITD_SET_MPL(x) (x)
+#define EHCI_ITD_GET_MPL(x) (((x) >> 0) & 0x7FF)
+ volatile uint32_t itd_bp_hi[7];
+/*
+ * Extra information needed:
+ */
+ uint32_t itd_self;
+ struct ehci_itd *next;
+ struct ehci_itd *prev;
+ struct ehci_itd *obj_next;
+ struct usb2_page_cache *page_cache;
+} __aligned(EHCI_ITD_ALIGN);
+
+typedef struct ehci_itd ehci_itd_t;
+
+/*
+ * Split Transaction Isochronous Transfer Descriptor. This descriptor is used
+ * for full speed transfers only.
+ */
+struct ehci_sitd {
+ volatile uint32_t sitd_next;
+ volatile uint32_t sitd_portaddr;
+#define EHCI_SITD_SET_DIR_OUT (0 << 31)
+#define EHCI_SITD_SET_DIR_IN (1 << 31)
+#define EHCI_SITD_SET_ADDR(x) (x)
+#define EHCI_SITD_GET_ADDR(x) ((x) & 0x7F)
+#define EHCI_SITD_SET_ENDPT(x) ((x) << 8)
+#define EHCI_SITD_GET_ENDPT(x) (((x) >> 8) & 0xF)
+#define EHCI_SITD_GET_DIR(x) ((x) >> 31)
+#define EHCI_SITD_SET_PORT(x) ((x) << 24)
+#define EHCI_SITD_GET_PORT(x) (((x) >> 24) & 0x7F)
+#define EHCI_SITD_SET_HUBA(x) ((x) << 16)
+#define EHCI_SITD_GET_HUBA(x) (((x) >> 16) & 0x7F)
+ volatile uint32_t sitd_mask;
+#define EHCI_SITD_SET_SMASK(x) (x)
+#define EHCI_SITD_SET_CMASK(x) ((x) << 8)
+ volatile uint32_t sitd_status;
+#define EHCI_SITD_COMPLETE_SPLIT (1<<1)
+#define EHCI_SITD_START_SPLIT (0<<1)
+#define EHCI_SITD_MISSED_MICRO_FRAME (1<<2)
+#define EHCI_SITD_XACTERR (1<<3)
+#define EHCI_SITD_BABBLE (1<<4)
+#define EHCI_SITD_DATABUFERR (1<<5)
+#define EHCI_SITD_ERROR (1<<6)
+#define EHCI_SITD_ACTIVE (1<<7)
+#define EHCI_SITD_IOC (1<<31)
+#define EHCI_SITD_SET_LEN(len) ((len)<<16)
+#define EHCI_SITD_GET_LEN(x) (((x)>>16) & 0x3FF)
+ volatile uint32_t sitd_bp[2];
+ volatile uint32_t sitd_back;
+ volatile uint32_t sitd_bp_hi[2];
+/*
+ * Extra information needed:
+ */
+ uint32_t sitd_self;
+ struct ehci_sitd *next;
+ struct ehci_sitd *prev;
+ struct ehci_sitd *obj_next;
+ struct usb2_page_cache *page_cache;
+} __aligned(EHCI_SITD_ALIGN);
+
+typedef struct ehci_sitd ehci_sitd_t;
+
+/* Queue Element Transfer Descriptor */
+struct ehci_qtd {
+ volatile uint32_t qtd_next;
+ volatile uint32_t qtd_altnext;
+ volatile uint32_t qtd_status;
+#define EHCI_QTD_GET_STATUS(x) (((x) >> 0) & 0xff)
+#define EHCI_QTD_SET_STATUS(x) ((x) << 0)
+#define EHCI_QTD_ACTIVE 0x80
+#define EHCI_QTD_HALTED 0x40
+#define EHCI_QTD_BUFERR 0x20
+#define EHCI_QTD_BABBLE 0x10
+#define EHCI_QTD_XACTERR 0x08
+#define EHCI_QTD_MISSEDMICRO 0x04
+#define EHCI_QTD_SPLITXSTATE 0x02
+#define EHCI_QTD_PINGSTATE 0x01
+#define EHCI_QTD_STATERRS 0x74
+#define EHCI_QTD_GET_PID(x) (((x) >> 8) & 0x3)
+#define EHCI_QTD_SET_PID(x) ((x) << 8)
+#define EHCI_QTD_PID_OUT 0x0
+#define EHCI_QTD_PID_IN 0x1
+#define EHCI_QTD_PID_SETUP 0x2
+#define EHCI_QTD_GET_CERR(x) (((x) >> 10) & 0x3)
+#define EHCI_QTD_SET_CERR(x) ((x) << 10)
+#define EHCI_QTD_GET_C_PAGE(x) (((x) >> 12) & 0x7)
+#define EHCI_QTD_SET_C_PAGE(x) ((x) << 12)
+#define EHCI_QTD_GET_IOC(x) (((x) >> 15) & 0x1)
+#define EHCI_QTD_IOC 0x00008000
+#define EHCI_QTD_GET_BYTES(x) (((x) >> 16) & 0x7fff)
+#define EHCI_QTD_SET_BYTES(x) ((x) << 16)
+#define EHCI_QTD_GET_TOGGLE(x) (((x) >> 31) & 0x1)
+#define EHCI_QTD_SET_TOGGLE(x) ((x) << 31)
+#define EHCI_QTD_TOGGLE_MASK 0x80000000
+#define EHCI_QTD_NBUFFERS 5
+#define EHCI_QTD_PAYLOAD_MAX ((EHCI_QTD_NBUFFERS-1)*EHCI_PAGE_SIZE)
+ volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS];
+ volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS];
+/*
+ * Extra information needed:
+ */
+ struct ehci_qtd *alt_next;
+ struct ehci_qtd *obj_next;
+ struct usb2_page_cache *page_cache;
+ uint32_t qtd_self;
+ uint16_t len;
+} __aligned(EHCI_QTD_ALIGN);
+
+typedef struct ehci_qtd ehci_qtd_t;
+
+/* Queue Head Sub Structure */
+struct ehci_qh_sub {
+ volatile uint32_t qtd_next;
+ volatile uint32_t qtd_altnext;
+ volatile uint32_t qtd_status;
+ volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS];
+ volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS];
+} __aligned(4);
+
+/* Queue Head */
+struct ehci_qh {
+ volatile uint32_t qh_link;
+ volatile uint32_t qh_endp;
+#define EHCI_QH_GET_ADDR(x) (((x) >> 0) & 0x7f) /* endpoint addr */
+#define EHCI_QH_SET_ADDR(x) (x)
+#define EHCI_QH_ADDRMASK 0x0000007f
+#define EHCI_QH_GET_INACT(x) (((x) >> 7) & 0x01) /* inactivate on next */
+#define EHCI_QH_INACT 0x00000080
+#define EHCI_QH_GET_ENDPT(x) (((x) >> 8) & 0x0f) /* endpoint no */
+#define EHCI_QH_SET_ENDPT(x) ((x) << 8)
+#define EHCI_QH_GET_EPS(x) (((x) >> 12) & 0x03) /* endpoint speed */
+#define EHCI_QH_SET_EPS(x) ((x) << 12)
+#define EHCI_QH_SPEED_FULL 0x0
+#define EHCI_QH_SPEED_LOW 0x1
+#define EHCI_QH_SPEED_HIGH 0x2
+#define EHCI_QH_GET_DTC(x) (((x) >> 14) & 0x01) /* data toggle control */
+#define EHCI_QH_DTC 0x00004000
+#define EHCI_QH_GET_HRECL(x) (((x) >> 15) & 0x01) /* head of reclamation */
+#define EHCI_QH_HRECL 0x00008000
+#define EHCI_QH_GET_MPL(x) (((x) >> 16) & 0x7ff) /* max packet len */
+#define EHCI_QH_SET_MPL(x) ((x) << 16)
+#define EHCI_QH_MPLMASK 0x07ff0000
+#define EHCI_QH_GET_CTL(x) (((x) >> 27) & 0x01) /* control endpoint */
+#define EHCI_QH_CTL 0x08000000
+#define EHCI_QH_GET_NRL(x) (((x) >> 28) & 0x0f) /* NAK reload */
+#define EHCI_QH_SET_NRL(x) ((x) << 28)
+ volatile uint32_t qh_endphub;
+#define EHCI_QH_GET_SMASK(x) (((x) >> 0) & 0xff) /* intr sched mask */
+#define EHCI_QH_SET_SMASK(x) ((x) << 0)
+#define EHCI_QH_GET_CMASK(x) (((x) >> 8) & 0xff) /* split completion mask */
+#define EHCI_QH_SET_CMASK(x) ((x) << 8)
+#define EHCI_QH_GET_HUBA(x) (((x) >> 16) & 0x7f) /* hub address */
+#define EHCI_QH_SET_HUBA(x) ((x) << 16)
+#define EHCI_QH_GET_PORT(x) (((x) >> 23) & 0x7f) /* hub port */
+#define EHCI_QH_SET_PORT(x) ((x) << 23)
+#define EHCI_QH_GET_MULT(x) (((x) >> 30) & 0x03) /* pipe multiplier */
+#define EHCI_QH_SET_MULT(x) ((x) << 30)
+ volatile uint32_t qh_curqtd;
+ struct ehci_qh_sub qh_qtd;
+/*
+ * Extra information needed:
+ */
+ struct ehci_qh *next;
+ struct ehci_qh *prev;
+ struct ehci_qh *obj_next;
+ struct usb2_page_cache *page_cache;
+ uint32_t qh_self;
+} __aligned(EHCI_QH_ALIGN);
+
+typedef struct ehci_qh ehci_qh_t;
+
+/* Periodic Frame Span Traversal Node */
+struct ehci_fstn {
+ volatile uint32_t fstn_link;
+ volatile uint32_t fstn_back;
+} __aligned(EHCI_FSTN_ALIGN);
+
+typedef struct ehci_fstn ehci_fstn_t;
+
+struct ehci_hw_softc {
+ struct usb2_page_cache pframes_pc;
+ struct usb2_page_cache async_start_pc;
+ struct usb2_page_cache intr_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct usb2_page_cache isoc_hs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct usb2_page_cache isoc_fs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT];
+
+ struct usb2_page pframes_pg;
+ struct usb2_page async_start_pg;
+ struct usb2_page intr_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct usb2_page isoc_hs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct usb2_page isoc_fs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT];
+};
+
+struct ehci_config_desc {
+ struct usb2_config_descriptor confd;
+ struct usb2_interface_descriptor ifcd;
+ struct usb2_endpoint_descriptor endpd;
+} __packed;
+
+union ehci_hub_desc {
+ struct usb2_status stat;
+ struct usb2_port_status ps;
+ struct usb2_device_descriptor devd;
+ struct usb2_device_qualifier odevd;
+ struct usb2_hub_descriptor hubd;
+ uint8_t temp[128];
+};
+
+typedef struct ehci_softc {
+ struct ehci_hw_softc sc_hw;
+ struct usb2_bus sc_bus; /* base device */
+ struct usb2_callout sc_tmo_pcd;
+ union ehci_hub_desc sc_hub_desc;
+ struct usb2_sw_transfer sc_root_ctrl;
+ struct usb2_sw_transfer sc_root_intr;
+
+ struct usb2_device *sc_devices[EHCI_MAX_DEVICES];
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ struct ehci_qh *sc_async_p_last;
+ struct ehci_qh *sc_intr_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct ehci_sitd *sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct ehci_itd *sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ void *sc_intr_hdl;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ uint32_t sc_eintrs;
+ uint32_t sc_cmd; /* shadow of cmd register during
+ * suspend */
+
+ uint16_t sc_intr_stat[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ uint16_t sc_id_vendor; /* vendor ID for root hub */
+ uint16_t sc_flags; /* chip specific flags */
+#define EHCI_SCFLG_SETMODE 0x0001 /* set bridge mode again after init */
+#define EHCI_SCFLG_FORCESPEED 0x0002 /* force speed */
+#define EHCI_SCFLG_NORESTERM 0x0004 /* don't terminate reset sequence */
+#define EHCI_SCFLG_BIGEDESC 0x0008 /* big-endian byte order descriptors */
+#define EHCI_SCFLG_BIGEMMIO 0x0010 /* big-endian byte order MMIO */
+#define EHCI_SCFLG_TT 0x0020 /* transaction translator present */
+
+ uint8_t sc_offs; /* offset to operational registers */
+ uint8_t sc_doorbell_disable; /* set on doorbell failure */
+ uint8_t sc_noport;
+ uint8_t sc_addr; /* device address */
+ uint8_t sc_conf; /* device configuration */
+ uint8_t sc_isreset;
+ uint8_t sc_hub_idata[8];
+
+ char sc_vendor[16]; /* vendor string for root hub */
+
+} ehci_softc_t;
+
+#define EREAD1(sc, a) bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (a))
+#define EREAD2(sc, a) bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a))
+#define EREAD4(sc, a) bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a))
+#define EWRITE1(sc, a, x) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x))
+#define EWRITE2(sc, a, x) \
+ bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x))
+#define EWRITE4(sc, a, x) \
+ bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x))
+#define EOREAD1(sc, a) \
+ bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a))
+#define EOREAD2(sc, a) \
+ bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a))
+#define EOREAD4(sc, a) \
+ bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a))
+#define EOWRITE1(sc, a, x) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x))
+#define EOWRITE2(sc, a, x) \
+ bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x))
+#define EOWRITE4(sc, a, x) \
+ bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x))
+
+usb2_bus_mem_cb_t ehci_iterate_hw_softc;
+
+usb2_error_t ehci_init(ehci_softc_t *sc);
+void ehci_detach(struct ehci_softc *sc);
+void ehci_suspend(struct ehci_softc *sc);
+void ehci_resume(struct ehci_softc *sc);
+void ehci_shutdown(ehci_softc_t *sc);
+void ehci_interrupt(ehci_softc_t *sc);
+
+#endif /* _EHCI_H_ */
diff --git a/sys/dev/usb/controller/ehci_ixp4xx.c b/sys/dev/usb/controller/ehci_ixp4xx.c
new file mode 100644
index 000000000000..b369d47a3579
--- /dev/null
+++ b/sys/dev/usb/controller/ehci_ixp4xx.c
@@ -0,0 +1,348 @@
+/*-
+ * Copyright (c) 2008 Sam Leffler. 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 ``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 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.
+ */
+
+/*
+ * IXP435 attachment driver for the USB Enhanced Host Controller.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_bus.h"
+
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ehci.h>
+
+#include <arm/xscale/ixp425/ixp425reg.h>
+#include <arm/xscale/ixp425/ixp425var.h>
+
+#define EHCI_VENDORID_IXP4XX 0x42fa05
+#define EHCI_HC_DEVSTR "IXP4XX Integrated USB 2.0 controller"
+
+struct ixp_ehci_softc {
+ ehci_softc_t base; /* storage for EHCI code */
+ bus_space_tag_t iot;
+ bus_space_handle_t ioh;
+ struct bus_space tag; /* tag for private bus space ops */
+};
+
+static device_attach_t ehci_ixp_attach;
+static device_detach_t ehci_ixp_detach;
+static device_shutdown_t ehci_ixp_shutdown;
+static device_suspend_t ehci_ixp_suspend;
+static device_resume_t ehci_ixp_resume;
+
+static uint8_t ehci_bs_r_1(void *, bus_space_handle_t, bus_size_t);
+static void ehci_bs_w_1(void *, bus_space_handle_t, bus_size_t, u_int8_t);
+static uint16_t ehci_bs_r_2(void *, bus_space_handle_t, bus_size_t);
+static void ehci_bs_w_2(void *, bus_space_handle_t, bus_size_t, uint16_t);
+static uint32_t ehci_bs_r_4(void *, bus_space_handle_t, bus_size_t);
+static void ehci_bs_w_4(void *, bus_space_handle_t, bus_size_t, uint32_t);
+
+static int
+ehci_ixp_suspend(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_suspend(self);
+ if (err)
+ return (err);
+ ehci_suspend(sc);
+ return (0);
+}
+
+static int
+ehci_ixp_resume(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+
+ ehci_resume(sc);
+
+ bus_generic_resume(self);
+
+ return (0);
+}
+
+static int
+ehci_ixp_shutdown(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_shutdown(self);
+ if (err)
+ return (err);
+ ehci_shutdown(sc);
+
+ return (0);
+}
+
+static int
+ehci_ixp_probe(device_t self)
+{
+
+ device_set_desc(self, EHCI_HC_DEVSTR);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+ehci_ixp_attach(device_t self)
+{
+ struct ixp_ehci_softc *isc = device_get_softc(self);
+ ehci_softc_t *sc = &isc->base;
+ int err;
+ int rid;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_bus_mem_alloc_all(&sc->sc_bus,
+ USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+
+ sc->sc_bus.usbrev = USB_REV_2_0;
+
+ /* NB: hints fix the memory location and irq */
+
+ rid = 0;
+ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+ if (!sc->sc_io_res) {
+ device_printf(self, "Could not map memory\n");
+ goto error;
+ }
+
+ /*
+ * Craft special resource for bus space ops that handle
+ * byte-alignment of non-word addresses. Also, since
+ * we're already intercepting bus space ops we handle
+ * the register window offset that could otherwise be
+ * done with bus_space_subregion.
+ */
+ isc->iot = rman_get_bustag(sc->sc_io_res);
+ isc->tag.bs_cookie = isc->iot;
+ /* read single */
+ isc->tag.bs_r_1 = ehci_bs_r_1,
+ isc->tag.bs_r_2 = ehci_bs_r_2,
+ isc->tag.bs_r_4 = ehci_bs_r_4,
+ /* write (single) */
+ isc->tag.bs_w_1 = ehci_bs_w_1,
+ isc->tag.bs_w_2 = ehci_bs_w_2,
+ isc->tag.bs_w_4 = ehci_bs_w_4,
+
+ sc->sc_io_tag = &isc->tag;
+ sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = IXP435_USB1_SIZE - 0x100;
+
+ rid = 0;
+ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ RF_ACTIVE);
+ if (sc->sc_irq_res == NULL) {
+ device_printf(self, "Could not allocate irq\n");
+ goto error;
+ }
+ sc->sc_bus.bdev = device_add_child(self, "usbus", -1);
+ if (!sc->sc_bus.bdev) {
+ device_printf(self, "Could not add USB device\n");
+ goto error;
+ }
+ device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
+ device_set_desc(sc->sc_bus.bdev, EHCI_HC_DEVSTR);
+
+ sprintf(sc->sc_vendor, "Intel");
+
+
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)(void *)ehci_interrupt, sc, &sc->sc_intr_hdl);
+ if (err) {
+ device_printf(self, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+
+ /*
+ * Arrange to force Host mode, select big-endian byte alignment,
+ * and arrange to not terminate reset operations (the adapter
+ * will ignore it if we do but might as well save a reg write).
+ * Also, the controller has an embedded Transaction Translator
+ * which means port speed must be read from the Port Status
+ * register following a port enable.
+ */
+ sc->sc_flags |= EHCI_SCFLG_TT
+ | EHCI_SCFLG_SETMODE
+ | EHCI_SCFLG_BIGEDESC
+ | EHCI_SCFLG_BIGEMMIO
+ | EHCI_SCFLG_NORESTERM
+ ;
+
+ err = ehci_init(sc);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ }
+ if (err) {
+ device_printf(self, "USB init failed err=%d\n", err);
+ goto error;
+ }
+ return (0);
+
+error:
+ ehci_ixp_detach(self);
+ return (ENXIO);
+}
+
+static int
+ehci_ixp_detach(device_t self)
+{
+ struct ixp_ehci_softc *isc = device_get_softc(self);
+ ehci_softc_t *sc = &isc->base;
+ device_t bdev;
+ int err;
+
+ if (sc->sc_bus.bdev) {
+ bdev = sc->sc_bus.bdev;
+ device_detach(bdev);
+ device_delete_child(self, bdev);
+ }
+ /* during module unload there are lots of children leftover */
+ device_delete_all_children(self);
+
+ /*
+ * disable interrupts that might have been switched on in ehci_init
+ */
+ if (sc->sc_io_res) {
+ EWRITE4(sc, EHCI_USBINTR, 0);
+ }
+
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ /*
+ * only call ehci_detach() after ehci_init()
+ */
+ ehci_detach(sc);
+
+ err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl);
+
+ if (err)
+ /* XXX or should we panic? */
+ device_printf(self, "Could not tear down irq, %d\n",
+ err);
+ sc->sc_intr_hdl = NULL;
+ }
+
+ if (sc->sc_irq_res) {
+ bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+ if (sc->sc_io_res) {
+ bus_release_resource(self, SYS_RES_MEMORY, 0,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb2_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+ return (0);
+}
+
+/*
+ * Bus space accessors for PIO operations.
+ */
+
+static uint8_t
+ehci_bs_r_1(void *t, bus_space_handle_t h, bus_size_t o)
+{
+ return bus_space_read_1((bus_space_tag_t) t, h,
+ 0x100 + (o &~ 3) + (3 - (o & 3)));
+}
+
+static void
+ehci_bs_w_1(void *t, bus_space_handle_t h, bus_size_t o, u_int8_t v)
+{
+ panic("%s", __func__);
+}
+
+static uint16_t
+ehci_bs_r_2(void *t, bus_space_handle_t h, bus_size_t o)
+{
+ return bus_space_read_2((bus_space_tag_t) t, h,
+ 0x100 + (o &~ 3) + (2 - (o & 3)));
+}
+
+static void
+ehci_bs_w_2(void *t, bus_space_handle_t h, bus_size_t o, uint16_t v)
+{
+ panic("%s", __func__);
+}
+
+static uint32_t
+ehci_bs_r_4(void *t, bus_space_handle_t h, bus_size_t o)
+{
+ return bus_space_read_4((bus_space_tag_t) t, h, 0x100 + o);
+}
+
+static void
+ehci_bs_w_4(void *t, bus_space_handle_t h, bus_size_t o, uint32_t v)
+{
+ bus_space_write_4((bus_space_tag_t) t, h, 0x100 + o, v);
+}
+
+static device_method_t ehci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ehci_ixp_probe),
+ DEVMETHOD(device_attach, ehci_ixp_attach),
+ DEVMETHOD(device_detach, ehci_ixp_detach),
+ DEVMETHOD(device_suspend, ehci_ixp_suspend),
+ DEVMETHOD(device_resume, ehci_ixp_resume),
+ DEVMETHOD(device_shutdown, ehci_ixp_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+};
+
+static driver_t ehci_driver = {
+ "ehci",
+ ehci_methods,
+ sizeof(struct ixp_ehci_softc),
+};
+
+static devclass_t ehci_devclass;
+
+DRIVER_MODULE(ehci, ixp, ehci_driver, ehci_devclass, 0, 0);
+MODULE_DEPEND(ehci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ehci_mbus.c b/sys/dev/usb/controller/ehci_mbus.c
new file mode 100644
index 000000000000..66cf8cc58b8a
--- /dev/null
+++ b/sys/dev/usb/controller/ehci_mbus.c
@@ -0,0 +1,364 @@
+/*-
+ * Copyright (C) 2008 MARVELL INTERNATIONAL LTD.
+ * All rights reserved.
+ *
+ * Developed by Semihalf.
+ *
+ * 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.
+ * 3. Neither the name of MARVELL nor the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY 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 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.
+ */
+
+/*
+ * MBus attachment driver for the USB Enhanced Host Controller.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_bus.h"
+
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ehci.h>
+
+#include <arm/mv/mvreg.h>
+#include <arm/mv/mvvar.h>
+
+#define EHCI_VENDORID_MRVL 0x1286
+#define EHCI_HC_DEVSTR "Marvell Integrated USB 2.0 controller"
+
+static device_attach_t ehci_mbus_attach;
+static device_detach_t ehci_mbus_detach;
+static device_shutdown_t ehci_mbus_shutdown;
+static device_suspend_t ehci_mbus_suspend;
+static device_resume_t ehci_mbus_resume;
+
+static int err_intr(void *arg);
+
+static struct resource *irq_err;
+static void *ih_err;
+
+#define USB_BRIDGE_INTR_CAUSE 0x210
+#define USB_BRIDGE_INTR_MASK 0x214
+
+#define MV_USB_ADDR_DECODE_ERR (1 << 0)
+#define MV_USB_HOST_UNDERFLOW (1 << 1)
+#define MV_USB_HOST_OVERFLOW (1 << 2)
+#define MV_USB_DEVICE_UNDERFLOW (1 << 3)
+
+static int
+ehci_mbus_suspend(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_suspend(self);
+ if (err)
+ return (err);
+ ehci_suspend(sc);
+ return (0);
+}
+
+static int
+ehci_mbus_resume(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+
+ ehci_resume(sc);
+
+ bus_generic_resume(self);
+
+ return (0);
+}
+
+static int
+ehci_mbus_shutdown(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_shutdown(self);
+ if (err)
+ return (err);
+ ehci_shutdown(sc);
+
+ return (0);
+}
+
+static int
+ehci_mbus_probe(device_t self)
+{
+
+ device_set_desc(self, EHCI_HC_DEVSTR);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+ehci_mbus_attach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ bus_space_handle_t bsh;
+ int err;
+ int rid;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_bus_mem_alloc_all(&sc->sc_bus,
+ USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+
+ sc->sc_bus.usbrev = USB_REV_2_0;
+
+ rid = 0;
+ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+ if (!sc->sc_io_res) {
+ device_printf(self, "Could not map memory\n");
+ goto error;
+ }
+ sc->sc_io_tag = rman_get_bustag(sc->sc_io_res);
+ bsh = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = MV_USB_SIZE - MV_USB_HOST_OFST;
+
+ /*
+ * Marvell EHCI host controller registers start at certain offset within
+ * the whole USB registers range, so create a subregion for the host
+ * mode configuration purposes.
+ */
+ if (bus_space_subregion(sc->sc_io_tag, bsh, MV_USB_HOST_OFST,
+ sc->sc_io_size, &sc->sc_io_hdl) != 0)
+ panic("%s: unable to subregion USB host registers",
+ device_get_name(self));
+
+ rid = 0;
+ irq_err = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (irq_err == NULL) {
+ device_printf(self, "Could not allocate error irq\n");
+ ehci_mbus_detach(self);
+ return (ENXIO);
+ }
+
+ /*
+ * Notice: Marvell EHCI controller has TWO interrupt lines, so make sure to
+ * use the correct rid for the main one (controller interrupt) --
+ * refer to obio_devices[] for the right resource number to use here.
+ */
+ rid = 1;
+ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (sc->sc_irq_res == NULL) {
+ device_printf(self, "Could not allocate irq\n");
+ goto error;
+ }
+
+ sc->sc_bus.bdev = device_add_child(self, "usbus", -1);
+ if (!sc->sc_bus.bdev) {
+ device_printf(self, "Could not add USB device\n");
+ goto error;
+ }
+ device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
+ device_set_desc(sc->sc_bus.bdev, EHCI_HC_DEVSTR);
+
+ sprintf(sc->sc_vendor, "Marvell");
+
+ err = bus_setup_intr(self, irq_err, INTR_FAST | INTR_TYPE_BIO,
+ err_intr, NULL, sc, &ih_err);
+ if (err) {
+ device_printf(self, "Could not setup error irq, %d\n", err);
+ ih_err = NULL;
+ goto error;
+ }
+
+ EWRITE4(sc, USB_BRIDGE_INTR_MASK, MV_USB_ADDR_DECODE_ERR |
+ MV_USB_HOST_UNDERFLOW | MV_USB_HOST_OVERFLOW |
+ MV_USB_DEVICE_UNDERFLOW);
+
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)(void *)ehci_interrupt, sc, &sc->sc_intr_hdl);
+ if (err) {
+ device_printf(self, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+
+ /*
+ * Workaround for Marvell integrated EHCI controller: reset of
+ * the EHCI core clears the USBMODE register, which sets the core in
+ * an undefined state (neither host nor agent), so it needs to be set
+ * again for proper operation.
+ *
+ * Refer to errata document MV-S500832-00D.pdf (p. 5.24 GL USB-2) for
+ * details.
+ */
+ sc->sc_flags |= EHCI_SCFLG_SETMODE;
+ if (bootverbose)
+ device_printf(self, "5.24 GL USB-2 workaround enabled\n");
+
+ /* XXX all MV chips need it? */
+ sc->sc_flags |= EHCI_SCFLG_FORCESPEED | EHCI_SCFLG_NORESTERM;
+
+ err = ehci_init(sc);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ }
+ if (err) {
+ device_printf(self, "USB init failed err=%d\n", err);
+ goto error;
+ }
+ return (0);
+
+error:
+ ehci_mbus_detach(self);
+ return (ENXIO);
+}
+
+static int
+ehci_mbus_detach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ device_t bdev;
+ int err;
+
+ if (sc->sc_bus.bdev) {
+ bdev = sc->sc_bus.bdev;
+ device_detach(bdev);
+ device_delete_child(self, bdev);
+ }
+ /* during module unload there are lots of children leftover */
+ device_delete_all_children(self);
+
+ /*
+ * disable interrupts that might have been switched on in ehci_init
+ */
+ if (sc->sc_io_res) {
+ EWRITE4(sc, EHCI_USBINTR, 0);
+ EWRITE4(sc, USB_BRIDGE_INTR_MASK, 0);
+ }
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ /*
+ * only call ehci_detach() after ehci_init()
+ */
+ ehci_detach(sc);
+
+ err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl);
+
+ if (err)
+ /* XXX or should we panic? */
+ device_printf(self, "Could not tear down irq, %d\n",
+ err);
+ sc->sc_intr_hdl = NULL;
+ }
+ if (irq_err && ih_err) {
+ err = bus_teardown_intr(self, irq_err, ih_err);
+
+ if (err)
+ device_printf(self, "Could not tear down irq, %d\n",
+ err);
+ ih_err = NULL;
+ }
+ if (irq_err) {
+ bus_release_resource(self, SYS_RES_IRQ, 0, irq_err);
+ irq_err = NULL;
+ }
+ if (sc->sc_irq_res) {
+ bus_release_resource(self, SYS_RES_IRQ, 1, sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+ if (sc->sc_io_res) {
+ bus_release_resource(self, SYS_RES_MEMORY, 0,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb2_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+ return (0);
+}
+
+static int
+err_intr(void *arg)
+{
+ ehci_softc_t *sc = arg;
+ unsigned int cause;
+
+ cause = EREAD4(sc, USB_BRIDGE_INTR_CAUSE);
+ if (cause) {
+ printf("IRQ ERR: cause: 0x%08x\n", cause);
+ if (cause & MV_USB_ADDR_DECODE_ERR)
+ printf("IRQ ERR: Address decoding error\n");
+ if (cause & MV_USB_HOST_UNDERFLOW)
+ printf("IRQ ERR: USB Host Underflow\n");
+ if (cause & MV_USB_HOST_OVERFLOW)
+ printf("IRQ ERR: USB Host Overflow\n");
+ if (cause & MV_USB_DEVICE_UNDERFLOW)
+ printf("IRQ ERR: USB Device Underflow\n");
+ if (cause & ~(MV_USB_ADDR_DECODE_ERR | MV_USB_HOST_UNDERFLOW |
+ MV_USB_HOST_OVERFLOW | MV_USB_DEVICE_UNDERFLOW))
+ printf("IRQ ERR: Unknown error\n");
+
+ EWRITE4(sc, USB_BRIDGE_INTR_CAUSE, 0);
+ }
+ return (FILTER_HANDLED);
+}
+
+static device_method_t ehci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ehci_mbus_probe),
+ DEVMETHOD(device_attach, ehci_mbus_attach),
+ DEVMETHOD(device_detach, ehci_mbus_detach),
+ DEVMETHOD(device_suspend, ehci_mbus_suspend),
+ DEVMETHOD(device_resume, ehci_mbus_resume),
+ DEVMETHOD(device_shutdown, ehci_mbus_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+};
+
+static driver_t ehci_driver = {
+ "ehci",
+ ehci_methods,
+ sizeof(ehci_softc_t),
+};
+
+static devclass_t ehci_devclass;
+
+DRIVER_MODULE(ehci, mbus, ehci_driver, ehci_devclass, 0, 0);
+MODULE_DEPEND(ehci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ehci_pci.c b/sys/dev/usb/controller/ehci_pci.c
new file mode 100644
index 000000000000..856ea6865675
--- /dev/null
+++ b/sys/dev/usb/controller/ehci_pci.c
@@ -0,0 +1,486 @@
+/*-
+ * Copyright (c) 1998 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (augustss@carlstedt.se) at
+ * Carlstedt Research & Technology.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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$");
+
+/*
+ * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller.
+ *
+ * The EHCI 1.0 spec can be found at
+ * http://developer.intel.com/technology/usb/download/ehci-r10.pdf
+ * and the USB 2.0 spec at
+ * http://www.usb.org/developers/docs/usb_20.zip
+ */
+
+/* The low level controller code for EHCI has been split into
+ * PCI probes and EHCI specific code. This was done to facilitate the
+ * sharing of code between *BSD's
+ */
+
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_pci.h>
+#include <dev/usb/controller/ehci.h>
+
+#define PCI_EHCI_VENDORID_ACERLABS 0x10b9
+#define PCI_EHCI_VENDORID_AMD 0x1022
+#define PCI_EHCI_VENDORID_APPLE 0x106b
+#define PCI_EHCI_VENDORID_ATI 0x1002
+#define PCI_EHCI_VENDORID_CMDTECH 0x1095
+#define PCI_EHCI_VENDORID_INTEL 0x8086
+#define PCI_EHCI_VENDORID_NEC 0x1033
+#define PCI_EHCI_VENDORID_OPTI 0x1045
+#define PCI_EHCI_VENDORID_PHILIPS 0x1131
+#define PCI_EHCI_VENDORID_SIS 0x1039
+#define PCI_EHCI_VENDORID_NVIDIA 0x12D2
+#define PCI_EHCI_VENDORID_NVIDIA2 0x10DE
+#define PCI_EHCI_VENDORID_VIA 0x1106
+
+#define PCI_EHCI_BASE_REG 0x10
+
+static void ehci_pci_takecontroller(device_t self);
+
+static device_probe_t ehci_pci_probe;
+static device_attach_t ehci_pci_attach;
+static device_detach_t ehci_pci_detach;
+static device_suspend_t ehci_pci_suspend;
+static device_resume_t ehci_pci_resume;
+static device_shutdown_t ehci_pci_shutdown;
+
+static int
+ehci_pci_suspend(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_suspend(self);
+ if (err)
+ return (err);
+ ehci_suspend(sc);
+ return (0);
+}
+
+static int
+ehci_pci_resume(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+
+ ehci_pci_takecontroller(self);
+ ehci_resume(sc);
+
+ bus_generic_resume(self);
+
+ return (0);
+}
+
+static int
+ehci_pci_shutdown(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_shutdown(self);
+ if (err)
+ return (err);
+ ehci_shutdown(sc);
+
+ return (0);
+}
+
+static const char *
+ehci_pci_match(device_t self)
+{
+ uint32_t device_id = pci_get_devid(self);
+
+ switch (device_id) {
+ case 0x268c8086:
+ return ("Intel 63XXESB USB 2.0 controller");
+
+ case 0x523910b9:
+ return "ALi M5239 USB 2.0 controller";
+
+ case 0x10227463:
+ return "AMD 8111 USB 2.0 controller";
+
+ case 0x20951022:
+ return ("AMD CS5536 (Geode) USB 2.0 controller");
+
+ case 0x43451002:
+ return "ATI SB200 USB 2.0 controller";
+ case 0x43731002:
+ return "ATI SB400 USB 2.0 controller";
+
+ case 0x25ad8086:
+ return "Intel 6300ESB USB 2.0 controller";
+ case 0x24cd8086:
+ return "Intel 82801DB/L/M (ICH4) USB 2.0 controller";
+ case 0x24dd8086:
+ return "Intel 82801EB/R (ICH5) USB 2.0 controller";
+ case 0x265c8086:
+ return "Intel 82801FB (ICH6) USB 2.0 controller";
+ case 0x27cc8086:
+ return "Intel 82801GB/R (ICH7) USB 2.0 controller";
+
+ case 0x28368086:
+ return "Intel 82801H (ICH8) USB 2.0 controller USB2-A";
+ case 0x283a8086:
+ return "Intel 82801H (ICH8) USB 2.0 controller USB2-B";
+ case 0x293a8086:
+ return "Intel 82801I (ICH9) USB 2.0 controller";
+ case 0x293c8086:
+ return "Intel 82801I (ICH9) USB 2.0 controller";
+
+ case 0x00e01033:
+ return ("NEC uPD 720100 USB 2.0 controller");
+
+ case 0x006810de:
+ return "NVIDIA nForce2 USB 2.0 controller";
+ case 0x008810de:
+ return "NVIDIA nForce2 Ultra 400 USB 2.0 controller";
+ case 0x00d810de:
+ return "NVIDIA nForce3 USB 2.0 controller";
+ case 0x00e810de:
+ return "NVIDIA nForce3 250 USB 2.0 controller";
+ case 0x005b10de:
+ return "NVIDIA nForce4 USB 2.0 controller";
+
+ case 0x15621131:
+ return "Philips ISP156x USB 2.0 controller";
+
+ case 0x31041106:
+ return ("VIA VT6202 USB 2.0 controller");
+
+ default:
+ break;
+ }
+
+ if ((pci_get_class(self) == PCIC_SERIALBUS)
+ && (pci_get_subclass(self) == PCIS_SERIALBUS_USB)
+ && (pci_get_progif(self) == PCI_INTERFACE_EHCI)) {
+ return ("EHCI (generic) USB 2.0 controller");
+ }
+ return (NULL); /* dunno */
+}
+
+static int
+ehci_pci_probe(device_t self)
+{
+ const char *desc = ehci_pci_match(self);
+
+ if (desc) {
+ device_set_desc(self, desc);
+ return (0);
+ } else {
+ return (ENXIO);
+ }
+}
+
+static int
+ehci_pci_attach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+ int rid;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_bus_mem_alloc_all(&sc->sc_bus,
+ USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+
+ pci_enable_busmaster(self);
+
+ switch (pci_read_config(self, PCI_USBREV, 1) & PCI_USB_REV_MASK) {
+ case PCI_USB_REV_PRE_1_0:
+ case PCI_USB_REV_1_0:
+ case PCI_USB_REV_1_1:
+ /*
+ * NOTE: some EHCI USB controllers have the wrong USB
+ * revision number. It appears those controllers are
+ * fully compliant so we just ignore this value in
+ * some common cases.
+ */
+ device_printf(self, "pre-2.0 USB revision (ignored)\n");
+ /* fallthrough */
+ case PCI_USB_REV_2_0:
+ sc->sc_bus.usbrev = USB_REV_2_0;
+ break;
+ default:
+ /* Quirk for Parallels Desktop 4.0 */
+ device_printf(self, "USB revision is unknown. Assuming v2.0.\n");
+ sc->sc_bus.usbrev = USB_REV_2_0;
+ break;
+ }
+
+ rid = PCI_CBMEM;
+ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (!sc->sc_io_res) {
+ device_printf(self, "Could not map memory\n");
+ goto error;
+ }
+ sc->sc_io_tag = rman_get_bustag(sc->sc_io_res);
+ sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res);
+
+ rid = 0;
+ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (sc->sc_irq_res == NULL) {
+ device_printf(self, "Could not allocate irq\n");
+ goto error;
+ }
+ sc->sc_bus.bdev = device_add_child(self, "usbus", -1);
+ if (!sc->sc_bus.bdev) {
+ device_printf(self, "Could not add USB device\n");
+ goto error;
+ }
+ device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
+
+ /*
+ * ehci_pci_match will never return NULL if ehci_pci_probe
+ * succeeded
+ */
+ device_set_desc(sc->sc_bus.bdev, ehci_pci_match(self));
+ switch (pci_get_vendor(self)) {
+ case PCI_EHCI_VENDORID_ACERLABS:
+ sprintf(sc->sc_vendor, "AcerLabs");
+ break;
+ case PCI_EHCI_VENDORID_AMD:
+ sprintf(sc->sc_vendor, "AMD");
+ break;
+ case PCI_EHCI_VENDORID_APPLE:
+ sprintf(sc->sc_vendor, "Apple");
+ break;
+ case PCI_EHCI_VENDORID_ATI:
+ sprintf(sc->sc_vendor, "ATI");
+ break;
+ case PCI_EHCI_VENDORID_CMDTECH:
+ sprintf(sc->sc_vendor, "CMDTECH");
+ break;
+ case PCI_EHCI_VENDORID_INTEL:
+ sprintf(sc->sc_vendor, "Intel");
+ break;
+ case PCI_EHCI_VENDORID_NEC:
+ sprintf(sc->sc_vendor, "NEC");
+ break;
+ case PCI_EHCI_VENDORID_OPTI:
+ sprintf(sc->sc_vendor, "OPTi");
+ break;
+ case PCI_EHCI_VENDORID_PHILIPS:
+ sprintf(sc->sc_vendor, "Philips");
+ break;
+ case PCI_EHCI_VENDORID_SIS:
+ sprintf(sc->sc_vendor, "SiS");
+ break;
+ case PCI_EHCI_VENDORID_NVIDIA:
+ case PCI_EHCI_VENDORID_NVIDIA2:
+ sprintf(sc->sc_vendor, "nVidia");
+ break;
+ case PCI_EHCI_VENDORID_VIA:
+ sprintf(sc->sc_vendor, "VIA");
+ break;
+ default:
+ if (bootverbose)
+ device_printf(self, "(New EHCI DeviceId=0x%08x)\n",
+ pci_get_devid(self));
+ sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self));
+ }
+
+#if (__FreeBSD_version >= 700031)
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)(void *)ehci_interrupt, sc, &sc->sc_intr_hdl);
+#else
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ (void *)(void *)ehci_interrupt, sc, &sc->sc_intr_hdl);
+#endif
+ if (err) {
+ device_printf(self, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+ ehci_pci_takecontroller(self);
+ err = ehci_init(sc);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ }
+ if (err) {
+ device_printf(self, "USB init failed err=%d\n", err);
+ goto error;
+ }
+ return (0);
+
+error:
+ ehci_pci_detach(self);
+ return (ENXIO);
+}
+
+static int
+ehci_pci_detach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ device_t bdev;
+
+ if (sc->sc_bus.bdev) {
+ bdev = sc->sc_bus.bdev;
+ device_detach(bdev);
+ device_delete_child(self, bdev);
+ }
+ /* during module unload there are lots of children leftover */
+ device_delete_all_children(self);
+
+ pci_disable_busmaster(self);
+
+ /*
+ * disable interrupts that might have been switched on in ehci_init
+ */
+ if (sc->sc_io_res) {
+ EWRITE4(sc, EHCI_USBINTR, 0);
+ }
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ /*
+ * only call ehci_detach() after ehci_init()
+ */
+ ehci_detach(sc);
+
+ int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl);
+
+ if (err)
+ /* XXX or should we panic? */
+ device_printf(self, "Could not tear down irq, %d\n",
+ err);
+ sc->sc_intr_hdl = NULL;
+ }
+ if (sc->sc_irq_res) {
+ bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+ if (sc->sc_io_res) {
+ bus_release_resource(self, SYS_RES_MEMORY, PCI_CBMEM,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb2_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+ return (0);
+}
+
+static void
+ehci_pci_takecontroller(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ uint32_t cparams;
+ uint32_t eec;
+ uint16_t to;
+ uint8_t eecp;
+ uint8_t bios_sem;
+
+ cparams = EREAD4(sc, EHCI_HCCPARAMS);
+
+ /* Synchronise with the BIOS if it owns the controller. */
+ for (eecp = EHCI_HCC_EECP(cparams); eecp != 0;
+ eecp = EHCI_EECP_NEXT(eec)) {
+ eec = pci_read_config(self, eecp, 4);
+ if (EHCI_EECP_ID(eec) != EHCI_EC_LEGSUP) {
+ continue;
+ }
+ bios_sem = pci_read_config(self, eecp +
+ EHCI_LEGSUP_BIOS_SEM, 1);
+ if (bios_sem == 0) {
+ continue;
+ }
+ device_printf(sc->sc_bus.bdev, "waiting for BIOS "
+ "to give up control\n");
+ pci_write_config(self, eecp +
+ EHCI_LEGSUP_OS_SEM, 1, 1);
+ to = 500;
+ while (1) {
+ bios_sem = pci_read_config(self, eecp +
+ EHCI_LEGSUP_BIOS_SEM, 1);
+ if (bios_sem == 0)
+ break;
+
+ if (--to == 0) {
+ device_printf(sc->sc_bus.bdev,
+ "timed out waiting for BIOS\n");
+ break;
+ }
+ usb2_pause_mtx(NULL, hz / 100); /* wait 10ms */
+ }
+ }
+}
+
+static driver_t ehci_driver =
+{
+ .name = "ehci",
+ .methods = (device_method_t[]){
+ /* device interface */
+ DEVMETHOD(device_probe, ehci_pci_probe),
+ DEVMETHOD(device_attach, ehci_pci_attach),
+ DEVMETHOD(device_detach, ehci_pci_detach),
+ DEVMETHOD(device_suspend, ehci_pci_suspend),
+ DEVMETHOD(device_resume, ehci_pci_resume),
+ DEVMETHOD(device_shutdown, ehci_pci_shutdown),
+ /* bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+ },
+ .size = sizeof(struct ehci_softc),
+};
+
+static devclass_t ehci_devclass;
+
+DRIVER_MODULE(ehci, pci, ehci_driver, ehci_devclass, 0, 0);
+DRIVER_MODULE(ehci, cardbus, ehci_driver, ehci_devclass, 0, 0);
+MODULE_DEPEND(ehci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/musb_otg.c b/sys/dev/usb/controller/musb_otg.c
new file mode 100644
index 000000000000..2194756140fa
--- /dev/null
+++ b/sys/dev/usb/controller/musb_otg.c
@@ -0,0 +1,2875 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 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.
+ */
+
+/*
+ * Thanks to Mentor Graphics for providing a reference driver for this
+ * USB chip at their homepage.
+ */
+
+/*
+ * This file contains the driver for the Mentor Graphics Inventra USB
+ * 2.0 High Speed Dual-Role controller.
+ *
+ * NOTE: The current implementation only supports Device Side Mode!
+ */
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.h>
+
+#define USB_DEBUG_VAR musbotgdebug
+
+#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_sw_transfer.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/musb_otg.h>
+
+#define MUSBOTG_INTR_ENDPT 1
+
+#define MUSBOTG_BUS2SC(bus) \
+ ((struct musbotg_softc *)(((uint8_t *)(bus)) - \
+ USB_P2U(&(((struct musbotg_softc *)0)->sc_bus))))
+
+#define MUSBOTG_PC2SC(pc) \
+ MUSBOTG_BUS2SC((pc)->tag_parent->info->bus)
+
+#if USB_DEBUG
+static int musbotgdebug = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, musbotg, CTLFLAG_RW, 0, "USB musbotg");
+SYSCTL_INT(_hw_usb2_musbotg, OID_AUTO, debug, CTLFLAG_RW,
+ &musbotgdebug, 0, "Debug level");
+#endif
+
+/* prototypes */
+
+struct usb2_bus_methods musbotg_bus_methods;
+struct usb2_pipe_methods musbotg_device_bulk_methods;
+struct usb2_pipe_methods musbotg_device_ctrl_methods;
+struct usb2_pipe_methods musbotg_device_intr_methods;
+struct usb2_pipe_methods musbotg_device_isoc_methods;
+struct usb2_pipe_methods musbotg_root_ctrl_methods;
+struct usb2_pipe_methods musbotg_root_intr_methods;
+
+static musbotg_cmd_t musbotg_setup_rx;
+static musbotg_cmd_t musbotg_setup_data_rx;
+static musbotg_cmd_t musbotg_setup_data_tx;
+static musbotg_cmd_t musbotg_setup_status;
+static musbotg_cmd_t musbotg_data_rx;
+static musbotg_cmd_t musbotg_data_tx;
+static void musbotg_device_done(struct usb2_xfer *, usb2_error_t);
+static void musbotg_do_poll(struct usb2_bus *);
+static void musbotg_root_ctrl_poll(struct musbotg_softc *);
+static void musbotg_standard_done(struct usb2_xfer *);
+static void musbotg_interrupt_poll(struct musbotg_softc *);
+
+static usb2_sw_transfer_func_t musbotg_root_intr_done;
+static usb2_sw_transfer_func_t musbotg_root_ctrl_done;
+
+/*
+ * Here is a configuration that the chip supports.
+ */
+static const struct usb2_hw_ep_profile musbotg_ep_profile[1] = {
+
+ [0] = {
+ .max_in_frame_size = 64,/* fixed */
+ .max_out_frame_size = 64, /* fixed */
+ .is_simplex = 1,
+ .support_control = 1,
+ }
+};
+
+static void
+musbotg_get_hw_ep_profile(struct usb2_device *udev,
+ const struct usb2_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+ struct musbotg_softc *sc;
+
+ sc = MUSBOTG_BUS2SC(udev->bus);
+
+ if (ep_addr == 0) {
+ /* control endpoint */
+ *ppf = musbotg_ep_profile;
+ } else if (ep_addr <= sc->sc_ep_max) {
+ /* other endpoints */
+ *ppf = sc->sc_hw_ep_profile + ep_addr;
+ } else {
+ *ppf = NULL;
+ }
+}
+
+static void
+musbotg_clocks_on(struct musbotg_softc *sc)
+{
+ if (sc->sc_flags.clocks_off &&
+ sc->sc_flags.port_powered) {
+
+ DPRINTFN(4, "\n");
+
+ if (sc->sc_clocks_on) {
+ (sc->sc_clocks_on) (sc->sc_clocks_arg);
+ }
+ sc->sc_flags.clocks_off = 0;
+
+ /* XXX enable Transceiver */
+ }
+}
+
+static void
+musbotg_clocks_off(struct musbotg_softc *sc)
+{
+ if (!sc->sc_flags.clocks_off) {
+
+ DPRINTFN(4, "\n");
+
+ /* XXX disable Transceiver */
+
+ if (sc->sc_clocks_off) {
+ (sc->sc_clocks_off) (sc->sc_clocks_arg);
+ }
+ sc->sc_flags.clocks_off = 1;
+ }
+}
+
+static void
+musbotg_pull_common(struct musbotg_softc *sc, uint8_t on)
+{
+ uint8_t temp;
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ if (on)
+ temp |= MUSB2_MASK_SOFTC;
+ else
+ temp &= ~MUSB2_MASK_SOFTC;
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
+}
+
+static void
+musbotg_pull_up(struct musbotg_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;
+ musbotg_pull_common(sc, 1);
+ }
+}
+
+static void
+musbotg_pull_down(struct musbotg_softc *sc)
+{
+ /* pulldown D+, if possible */
+
+ if (sc->sc_flags.d_pulled_up) {
+ sc->sc_flags.d_pulled_up = 0;
+ musbotg_pull_common(sc, 0);
+ }
+}
+
+static void
+musbotg_wakeup_peer(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ uint8_t temp;
+ uint8_t use_polling;
+
+ if (!(sc->sc_flags.status_suspend)) {
+ return;
+ }
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ temp |= MUSB2_MASK_RESUME;
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
+
+ /* wait 8 milliseconds */
+ if (use_polling) {
+ /* polling */
+ DELAY(8000);
+ } else {
+ /* Wait for reset to complete. */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+ }
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ temp &= ~MUSB2_MASK_RESUME;
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
+}
+
+static void
+musbotg_set_address(struct musbotg_softc *sc, uint8_t addr)
+{
+ DPRINTFN(4, "addr=%d\n", addr);
+ addr &= 0x7F;
+ MUSB2_WRITE_1(sc, MUSB2_REG_FADDR, addr);
+}
+
+static uint8_t
+musbotg_setup_rx(struct musbotg_td *td)
+{
+ struct musbotg_softc *sc;
+ struct usb2_device_request req;
+ uint16_t count;
+ uint8_t csr;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ /*
+ * NOTE: If DATAEND is set we should not call the
+ * callback, hence the status stage is not complete.
+ */
+ if (csr & MUSB2_MASK_CSR0L_DATAEND) {
+ /* wait for interrupt */
+ goto not_complete;
+ }
+ if (csr & MUSB2_MASK_CSR0L_SENTSTALL) {
+ /* clear SENTSTALL */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ /* get latest status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ /* update EP0 state */
+ sc->sc_ep0_busy = 0;
+ }
+ if (csr & MUSB2_MASK_CSR0L_SETUPEND) {
+ /* clear SETUPEND */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_SETUPEND_CLR);
+ /* get latest status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ /* update EP0 state */
+ sc->sc_ep0_busy = 0;
+ }
+ if (sc->sc_ep0_busy) {
+ /* abort any ongoing transfer */
+ if (!td->did_stall) {
+ DPRINTFN(4, "stalling\n");
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_SENDSTALL);
+ td->did_stall = 1;
+ }
+ goto not_complete;
+ }
+ if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) {
+ goto not_complete;
+ }
+ /* get the packet byte count */
+ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT);
+
+ /* 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(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), (void *)&req, sizeof(req));
+
+ /* copy data into real buffer */
+ usb2_copy_in(td->pc, 0, &req, sizeof(req));
+
+ td->offset = sizeof(req);
+ td->remainder = 0;
+
+ /* set pending command */
+ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR;
+
+ /* we need set stall or dataend after this */
+ sc->sc_ep0_busy = 1;
+
+ /* 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;
+ }
+ return (0); /* complete */
+
+not_complete:
+ return (1); /* not complete */
+}
+
+/* Control endpoint only data handling functions (RX/TX/SYNC) */
+
+static uint8_t
+musbotg_setup_data_rx(struct musbotg_td *td)
+{
+ struct usb2_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+ uint8_t got_short;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* check if a command is pending */
+ if (sc->sc_ep0_cmd) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd);
+ sc->sc_ep0_cmd = 0;
+ }
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ got_short = 0;
+
+ if (csr & (MUSB2_MASK_CSR0L_SETUPEND |
+ MUSB2_MASK_CSR0L_SENTSTALL)) {
+ if (td->remainder == 0) {
+ /*
+ * We are actually complete and have
+ * received the next SETUP
+ */
+ DPRINTFN(4, "faking complete\n");
+ return (0); /* complete */
+ }
+ /*
+ * USB Host Aborted the transfer.
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) {
+ return (1); /* not complete */
+ }
+ /* get the packet byte count */
+ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT);
+
+ /* verify the packet byte count */
+ if (count != td->max_frame_size) {
+ if (count < td->max_frame_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) {
+ uint32_t temp;
+
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+
+ temp = count & ~3;
+
+ if (temp) {
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf,
+ temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0),
+ (void *)(&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ usb2_copy_in(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* receive data */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR;
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+ /* write command - need more data */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_RXPKTRDY_CLR);
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_setup_data_tx(struct musbotg_td *td)
+{
+ struct usb2_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* check if a command is pending */
+ if (sc->sc_ep0_cmd) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd);
+ sc->sc_ep0_cmd = 0;
+ }
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ if (csr & (MUSB2_MASK_CSR0L_SETUPEND |
+ MUSB2_MASK_CSR0L_SENTSTALL)) {
+ /*
+ * The current transfer was aborted
+ * by the USB Host
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) {
+ return (1); /* not complete */
+ }
+ count = td->max_frame_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ while (count > 0) {
+ uint32_t temp;
+
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+
+ usb2_copy_out(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ temp = count & ~3;
+
+ if (temp) {
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf,
+ temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0),
+ ((void *)&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* transmit data */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_TXPKTRDY;
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+ /* write command */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_TXPKTRDY);
+
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_setup_status(struct musbotg_td *td)
+{
+ struct musbotg_softc *sc;
+ uint8_t csr;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ if (sc->sc_ep0_busy) {
+ sc->sc_ep0_busy = 0;
+ sc->sc_ep0_cmd |= MUSB2_MASK_CSR0L_DATAEND;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd);
+ sc->sc_ep0_cmd = 0;
+ }
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ if (csr & MUSB2_MASK_CSR0L_DATAEND) {
+ /* wait for interrupt */
+ return (1); /* not complete */
+ }
+ if (sc->sc_dv_addr != 0xFF) {
+ /* write function address */
+ musbotg_set_address(sc, sc->sc_dv_addr);
+ }
+ return (0); /* complete */
+}
+
+static uint8_t
+musbotg_data_rx(struct musbotg_td *td)
+{
+ struct usb2_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+ uint8_t to;
+ uint8_t got_short;
+
+ to = 8; /* don't loop forever! */
+ got_short = 0;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->ep_no);
+
+repeat:
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ /* clear overrun */
+ if (csr & MUSB2_MASK_CSRL_RXOVERRUN) {
+ /* make sure we don't clear "RXPKTRDY" */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXPKTRDY);
+ }
+ /* check status */
+ if (!(csr & MUSB2_MASK_CSRL_RXPKTRDY)) {
+ return (1); /* not complete */
+ }
+ /* get the packet byte count */
+ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT);
+
+ DPRINTFN(4, "count=0x%04x\n", count);
+
+ /*
+ * Check for short or invalid packet:
+ */
+ if (count != td->max_frame_size) {
+ if (count < td->max_frame_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) {
+ uint32_t temp;
+
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+
+ temp = count & ~3;
+
+ if (temp) {
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->ep_no), sc->sc_bounce_buf,
+ temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_read_multi_1(sc->sc_io_tag,
+ sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->ep_no),
+ ((void *)&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ usb2_copy_in(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* receive data */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->ep_no), 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 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0);
+
+ /* 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
+musbotg_data_tx(struct musbotg_td *td)
+{
+ struct usb2_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+ uint8_t to;
+
+ to = 8; /* don't loop forever! */
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->ep_no);
+
+repeat:
+
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ if (csr & (MUSB2_MASK_CSRL_TXINCOMP |
+ MUSB2_MASK_CSRL_TXUNDERRUN)) {
+ /* clear status bits */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ }
+ if (csr & MUSB2_MASK_CSRL_TXPKTRDY) {
+ return (1); /* not complete */
+ }
+ /* check for short packet */
+ count = td->max_frame_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ while (count > 0) {
+ uint32_t temp;
+
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+
+ usb2_copy_out(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ temp = count & ~3;
+
+ if (temp) {
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag,
+ sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->ep_no),
+ sc->sc_bounce_buf, temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->ep_no),
+ ((void *)&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* transmit data */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer,
+ buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* write command */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXPKTRDY);
+
+ /* 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
+musbotg_xfer_do_fifo(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc;
+ struct musbotg_td *td;
+
+ DPRINTFN(8, "\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:
+ sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+
+ /* compute all actual lengths */
+
+ musbotg_standard_done(xfer);
+
+ return (0); /* complete */
+}
+
+static void
+musbotg_interrupt_poll(struct musbotg_softc *sc)
+{
+ struct usb2_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (!musbotg_xfer_do_fifo(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+void
+musbotg_vbus_interrupt(struct musbotg_softc *sc, uint8_t is_on)
+{
+ DPRINTFN(4, "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 */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &musbotg_root_intr_done);
+ }
+ } 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 */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &musbotg_root_intr_done);
+ }
+ }
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+musbotg_interrupt(struct musbotg_softc *sc)
+{
+ uint16_t rx_status;
+ uint16_t tx_status;
+ uint8_t usb_status;
+ uint8_t temp;
+ uint8_t to = 2;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+repeat:
+
+ /* read all interrupt registers */
+ usb_status = MUSB2_READ_1(sc, MUSB2_REG_INTUSB);
+
+ /* read all FIFO interrupts */
+ rx_status = MUSB2_READ_2(sc, MUSB2_REG_INTRX);
+ tx_status = MUSB2_READ_2(sc, MUSB2_REG_INTTX);
+
+ /* check for any bus state change interrupts */
+
+ if (usb_status & (MUSB2_MASK_IRESET |
+ MUSB2_MASK_IRESUME | MUSB2_MASK_ISUSP)) {
+
+ DPRINTFN(4, "real bus interrupt 0x%08x\n", usb_status);
+
+ if (usb_status & MUSB2_MASK_IRESET) {
+
+ /* 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;
+
+ /* determine line speed */
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ if (temp & MUSB2_MASK_HSMODE)
+ sc->sc_flags.status_high_speed = 1;
+ else
+ sc->sc_flags.status_high_speed = 0;
+
+ /*
+ * After reset all interrupts are on and we need to
+ * turn them off!
+ */
+ temp = MUSB2_MASK_IRESET;
+ /* disable resume interrupt */
+ temp &= ~MUSB2_MASK_IRESUME;
+ /* enable suspend interrupt */
+ temp |= MUSB2_MASK_ISUSP;
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp);
+ /* disable TX and RX interrupts */
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0);
+ }
+ /*
+ * 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 (usb_status & MUSB2_MASK_IRESUME) {
+ if (sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 1;
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE);
+ /* disable resume interrupt */
+ temp &= ~MUSB2_MASK_IRESUME;
+ /* enable suspend interrupt */
+ temp |= MUSB2_MASK_ISUSP;
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp);
+ }
+ } else if (usb_status & MUSB2_MASK_ISUSP) {
+ if (!sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 1;
+ sc->sc_flags.change_suspend = 1;
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE);
+ /* disable suspend interrupt */
+ temp &= ~MUSB2_MASK_ISUSP;
+ /* enable resume interrupt */
+ temp |= MUSB2_MASK_IRESUME;
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp);
+ }
+ }
+ /* complete root HUB interrupt endpoint */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &musbotg_root_intr_done);
+ }
+ /* check for any endpoint interrupts */
+
+ if (rx_status || tx_status) {
+ DPRINTFN(4, "real endpoint interrupt "
+ "rx=0x%04x, tx=0x%04x\n", rx_status, tx_status);
+ }
+ /* poll one time regardless of FIFO status */
+
+ musbotg_interrupt_poll(sc);
+
+ if (--to)
+ goto repeat;
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+musbotg_setup_standard_chain_sub(struct musbotg_std_temp *temp)
+{
+ struct musbotg_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 = 0;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+}
+
+static void
+musbotg_setup_standard_chain(struct usb2_xfer *xfer)
+{
+ struct musbotg_std_temp temp;
+ struct musbotg_softc *sc;
+ struct musbotg_td *td;
+ uint32_t x;
+ uint8_t ep_no;
+
+ DPRINTFN(8, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpoint),
+ xfer->sumlen, usb2_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.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.offset = 0;
+
+ sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ ep_no = (xfer->endpoint & UE_ADDR);
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+
+ temp.func = &musbotg_setup_rx;
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.short_pkt = temp.len ? 1 : 0;
+
+ musbotg_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ if (x != xfer->nframes) {
+ if (xfer->endpoint & UE_DIR_IN) {
+ if (xfer->flags_int.control_xfr)
+ temp.func = &musbotg_setup_data_tx;
+ else
+ temp.func = &musbotg_data_tx;
+ } else {
+ if (xfer->flags_int.control_xfr)
+ temp.func = &musbotg_setup_data_rx;
+ else
+ temp.func = &musbotg_data_rx;
+ }
+
+ /* setup "pc" pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ while (x != xfer->nframes) {
+
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+
+ x++;
+
+ if (x == xfer->nframes) {
+ 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;
+ }
+
+ musbotg_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;
+ }
+ }
+
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+
+ /* check if we should append a status stage */
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ temp.func = &musbotg_setup_status;
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ musbotg_setup_standard_chain_sub(&temp);
+ }
+ /* must have at least one frame! */
+ td = temp.td;
+ xfer->td_transfer_last = td;
+}
+
+static void
+musbotg_timeout(void *arg)
+{
+ struct usb2_xfer *xfer = arg;
+
+ DPRINTFN(1, "xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ musbotg_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+musbotg_ep_int_set(struct usb2_xfer *xfer, uint8_t on)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ uint16_t temp;
+ uint8_t ep_no = xfer->endpoint & UE_ADDR;
+
+ /*
+ * Only enable the endpoint interrupt when we are
+ * actually waiting for data, hence we are dealing
+ * with level triggered interrupts !
+ */
+ if (ep_no == 0) {
+ temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE);
+ if (on)
+ temp |= MUSB2_MASK_EPINT(0);
+ else
+ temp &= ~MUSB2_MASK_EPINT(0);
+
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp);
+ } else {
+ if (USB_GET_DATA_ISREAD(xfer)) {
+ temp = MUSB2_READ_2(sc, MUSB2_REG_INTRXE);
+ if (on)
+ temp |= MUSB2_MASK_EPINT(ep_no);
+ else
+ temp &= ~MUSB2_MASK_EPINT(ep_no);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, temp);
+
+ } else {
+ temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE);
+ if (on)
+ temp |= MUSB2_MASK_EPINT(ep_no);
+ else
+ temp &= ~MUSB2_MASK_EPINT(ep_no);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp);
+ }
+ }
+}
+
+static void
+musbotg_start_standard_chain(struct usb2_xfer *xfer)
+{
+ DPRINTFN(8, "\n");
+
+ /* poll one time */
+ if (musbotg_xfer_do_fifo(xfer)) {
+
+ musbotg_ep_int_set(xfer, 1);
+
+ DPRINTFN(14, "enabled interrupts on endpoint\n");
+
+ /* put transfer on interrupt queue */
+ usb2_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usb2_transfer_timeout_ms(xfer,
+ &musbotg_timeout, xfer->timeout);
+ }
+ }
+}
+
+static void
+musbotg_root_intr_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(8, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_PRE_DATA) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ musbotg_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* setup buffer */
+ std->ptr = sc->sc_hub_idata;
+ std->len = sizeof(sc->sc_hub_idata);
+
+ /* set port bit */
+ sc->sc_hub_idata[0] = 0x02; /* we only have one port */
+
+done:
+ return;
+}
+
+static usb2_error_t
+musbotg_standard_done_sub(struct usb2_xfer *xfer)
+{
+ struct musbotg_td *td;
+ uint32_t len;
+ uint8_t error;
+
+ DPRINTFN(8, "\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
+musbotg_standard_done(struct usb2_xfer *xfer)
+{
+ usb2_error_t err = 0;
+
+ DPRINTFN(12, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+
+ if (xfer->flags_int.control_hdr) {
+
+ err = musbotg_standard_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+
+ err = musbotg_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 = musbotg_standard_done_sub(xfer);
+ }
+done:
+ musbotg_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * musbotg_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_done(struct usb2_xfer *xfer, usb2_error_t error)
+{
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n",
+ xfer, xfer->pipe, error);
+
+ if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) {
+
+ musbotg_ep_int_set(xfer, 0);
+
+ DPRINTFN(14, "disabled interrupts on endpoint\n");
+ }
+ /* dequeue transfer and start next transfer */
+ usb2_transfer_done(xfer, error);
+}
+
+static void
+musbotg_set_stall(struct usb2_device *udev, struct usb2_xfer *xfer,
+ struct usb2_pipe *pipe)
+{
+ struct musbotg_softc *sc;
+ uint8_t ep_no;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(4, "pipe=%p\n", pipe);
+
+ if (xfer) {
+ /* cancel any ongoing transfers */
+ musbotg_device_done(xfer, USB_ERR_STALLED);
+ }
+ /* set FORCESTALL */
+ sc = MUSBOTG_BUS2SC(udev->bus);
+
+ ep_no = (pipe->edesc->bEndpointAddress & UE_ADDR);
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no);
+
+ if (pipe->edesc->bEndpointAddress & UE_DIR_IN) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXSENDSTALL);
+ } else {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXSENDSTALL);
+ }
+}
+
+static void
+musbotg_clear_stall_sub(struct musbotg_softc *sc, uint16_t wMaxPacket,
+ uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir)
+{
+ uint16_t mps;
+ uint16_t temp;
+ uint8_t csr;
+
+ if (ep_type == UE_CONTROL) {
+ /* clearing stall is not needed */
+ return;
+ }
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no);
+
+ /* compute max frame size */
+ mps = wMaxPacket & 0x7FF;
+ switch ((wMaxPacket >> 11) & 3) {
+ case 1:
+ mps *= 2;
+ break;
+ case 2:
+ mps *= 3;
+ break;
+ default:
+ break;
+ }
+
+ if (ep_dir == UE_DIR_IN) {
+
+ temp = 0;
+
+ /* Configure endpoint */
+ switch (ep_type) {
+ case UE_INTERRUPT:
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH,
+ MUSB2_MASK_CSRH_TXMODE | temp);
+ break;
+ case UE_ISOCHRONOUS:
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH,
+ MUSB2_MASK_CSRH_TXMODE |
+ MUSB2_MASK_CSRH_TXISO | temp);
+ break;
+ case UE_BULK:
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH,
+ MUSB2_MASK_CSRH_TXMODE | temp);
+ break;
+ default:
+ break;
+ }
+
+ /* Need to flush twice in case of double bufring */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXFFLUSH);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXFFLUSH);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ }
+ }
+ /* reset data toggle */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXDT_CLR);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ /* set double/single buffering */
+ temp = MUSB2_READ_2(sc, MUSB2_REG_TXDBDIS);
+ if (mps <= (sc->sc_hw_ep_profile[ep_no].
+ max_in_frame_size / 2)) {
+ /* double buffer */
+ temp &= ~(1 << ep_no);
+ } else {
+ /* single buffer */
+ temp |= (1 << ep_no);
+ }
+ MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, temp);
+
+ /* clear sent stall */
+ if (csr & MUSB2_MASK_CSRL_TXSENTSTALL) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ }
+ } else {
+
+ temp = 0;
+
+ /* Configure endpoint */
+ switch (ep_type) {
+ case UE_INTERRUPT:
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH,
+ MUSB2_MASK_CSRH_RXNYET | temp);
+ break;
+ case UE_ISOCHRONOUS:
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH,
+ MUSB2_MASK_CSRH_RXNYET |
+ MUSB2_MASK_CSRH_RXISO | temp);
+ break;
+ case UE_BULK:
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, temp);
+ break;
+ default:
+ break;
+ }
+
+ /* Need to flush twice in case of double bufring */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+ if (csr & MUSB2_MASK_CSRL_RXPKTRDY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXFFLUSH);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+ if (csr & MUSB2_MASK_CSRL_RXPKTRDY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXFFLUSH);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+ }
+ }
+ /* reset data toggle */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXDT_CLR);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+
+ /* set double/single buffering */
+ temp = MUSB2_READ_2(sc, MUSB2_REG_RXDBDIS);
+ if (mps <= (sc->sc_hw_ep_profile[ep_no].
+ max_out_frame_size / 2)) {
+ /* double buffer */
+ temp &= ~(1 << ep_no);
+ } else {
+ /* single buffer */
+ temp |= (1 << ep_no);
+ }
+ MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, temp);
+
+ /* clear sent stall */
+ if (csr & MUSB2_MASK_CSRL_RXSENTSTALL) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0);
+ }
+ }
+}
+
+static void
+musbotg_clear_stall(struct usb2_device *udev, struct usb2_pipe *pipe)
+{
+ struct musbotg_softc *sc;
+ struct usb2_endpoint_descriptor *ed;
+
+ DPRINTFN(4, "pipe=%p\n", pipe);
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ /* check mode */
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ /* get softc */
+ sc = MUSBOTG_BUS2SC(udev->bus);
+
+ /* get endpoint descriptor */
+ ed = pipe->edesc;
+
+ /* reset endpoint */
+ musbotg_clear_stall_sub(sc,
+ UGETW(ed->wMaxPacketSize),
+ (ed->bEndpointAddress & UE_ADDR),
+ (ed->bmAttributes & UE_XFERTYPE),
+ (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb2_error_t
+musbotg_init(struct musbotg_softc *sc)
+{
+ struct usb2_hw_ep_profile *pf;
+ uint8_t nrx;
+ uint8_t ntx;
+ uint8_t temp;
+ uint8_t fsize;
+ uint8_t frx;
+ uint8_t ftx;
+
+ DPRINTFN(1, "start\n");
+
+ /* set up the bus structure */
+ sc->sc_bus.usbrev = USB_REV_2_0;
+ sc->sc_bus.methods = &musbotg_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 */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+
+ /* disable all interrupts */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0);
+
+ /* disable pullup */
+
+ musbotg_pull_common(sc, 0);
+
+ /* wait a little bit (10ms) */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
+ /* disable double packet buffering */
+ MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, 0xFFFF);
+ MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, 0xFFFF);
+
+ /* enable HighSpeed and ISO Update flags */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER,
+ MUSB2_MASK_HSENAB | MUSB2_MASK_ISOUPD);
+
+ /* clear Session bit, if set */
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL);
+ temp &= ~MUSB2_MASK_SESS;
+ MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp);
+
+ DPRINTF("DEVCTL=0x%02x\n", temp);
+
+ /* disable testmode */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_TESTMODE, 0);
+
+ /* set default value */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_MISC, 0);
+
+ /* select endpoint index 0 */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* read out number of endpoints */
+
+ nrx =
+ (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) / 16);
+
+ ntx =
+ (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) % 16);
+
+ /* these numbers exclude the control endpoint */
+
+ DPRINTFN(2, "RX/TX endpoints: %u/%u\n", nrx, ntx);
+
+ sc->sc_ep_max = (nrx > ntx) ? nrx : ntx;
+ if (sc->sc_ep_max == 0) {
+ DPRINTFN(2, "ERROR: Looks like the clocks are off!\n");
+ }
+ /* read out configuration data */
+
+ sc->sc_conf_data = MUSB2_READ_1(sc, MUSB2_REG_CONFDATA);
+
+ DPRINTFN(2, "Config Data: 0x%02x\n",
+ sc->sc_conf_data);
+
+ DPRINTFN(2, "HW version: 0x%04x\n",
+ MUSB2_READ_1(sc, MUSB2_REG_HWVERS));
+
+ /* initialise endpoint profiles */
+
+ for (temp = 1; temp <= sc->sc_ep_max; temp++) {
+ pf = sc->sc_hw_ep_profile + temp;
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, temp);
+
+ fsize = MUSB2_READ_1(sc, MUSB2_REG_FSIZE);
+ frx = (fsize & MUSB2_MASK_RX_FSIZE) / 16;;
+ ftx = (fsize & MUSB2_MASK_TX_FSIZE);
+
+ DPRINTF("Endpoint %u FIFO size: IN=%u, OUT=%u\n",
+ temp, pf->max_in_frame_size,
+ pf->max_out_frame_size);
+
+ if (frx && ftx && (temp <= nrx) && (temp <= ntx)) {
+ pf->max_in_frame_size = 1 << ftx;
+ pf->max_out_frame_size = 1 << frx;
+ pf->is_simplex = 0; /* duplex */
+ pf->support_multi_buffer = 1;
+ pf->support_bulk = 1;
+ pf->support_interrupt = 1;
+ pf->support_isochronous = 1;
+ pf->support_in = 1;
+ pf->support_out = 1;
+ } else if (frx && (temp <= nrx)) {
+ pf->max_out_frame_size = 1 << frx;
+ pf->is_simplex = 1; /* simplex */
+ pf->support_multi_buffer = 1;
+ pf->support_bulk = 1;
+ pf->support_interrupt = 1;
+ pf->support_isochronous = 1;
+ pf->support_out = 1;
+ } else if (ftx && (temp <= ntx)) {
+ pf->max_in_frame_size = 1 << ftx;
+ pf->is_simplex = 1; /* simplex */
+ pf->support_multi_buffer = 1;
+ pf->support_bulk = 1;
+ pf->support_interrupt = 1;
+ pf->support_isochronous = 1;
+ pf->support_in = 1;
+ }
+ }
+
+ /* turn on default interrupts */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE,
+ MUSB2_MASK_IRESET);
+
+ musbotg_clocks_off(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch any lost interrupts */
+
+ musbotg_do_poll(&sc->sc_bus);
+
+ return (0); /* success */
+}
+
+void
+musbotg_uninit(struct musbotg_softc *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* disable all interrupts */
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 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;
+
+ musbotg_pull_down(sc);
+ musbotg_clocks_off(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+musbotg_suspend(struct musbotg_softc *sc)
+{
+ return;
+}
+
+void
+musbotg_resume(struct musbotg_softc *sc)
+{
+ return;
+}
+
+static void
+musbotg_do_poll(struct usb2_bus *bus)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ musbotg_interrupt_poll(sc);
+ musbotg_root_ctrl_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * musbotg bulk support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_bulk_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_bulk_close(struct usb2_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_device_bulk_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_bulk_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ musbotg_setup_standard_chain(xfer);
+ musbotg_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods musbotg_device_bulk_methods =
+{
+ .open = musbotg_device_bulk_open,
+ .close = musbotg_device_bulk_close,
+ .enter = musbotg_device_bulk_enter,
+ .start = musbotg_device_bulk_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg control support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_ctrl_close(struct usb2_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_device_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_ctrl_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ musbotg_setup_standard_chain(xfer);
+ musbotg_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods musbotg_device_ctrl_methods =
+{
+ .open = musbotg_device_ctrl_open,
+ .close = musbotg_device_ctrl_close,
+ .enter = musbotg_device_ctrl_enter,
+ .start = musbotg_device_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg interrupt support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_intr_close(struct usb2_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_device_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_intr_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ musbotg_setup_standard_chain(xfer);
+ musbotg_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods musbotg_device_intr_methods =
+{
+ .open = musbotg_device_intr_open,
+ .close = musbotg_device_intr_close,
+ .enter = musbotg_device_intr_enter,
+ .start = musbotg_device_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_isoc_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_isoc_close(struct usb2_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_device_isoc_enter(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ uint32_t temp;
+ uint32_t nframes;
+ uint32_t fs_frames;
+
+ DPRINTFN(5, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->pipe->isoc_next, xfer->nframes);
+
+ /* get the current frame index */
+
+ nframes = MUSB2_READ_2(sc, MUSB2_REG_FRAME);
+
+ /*
+ * check if the frame index is within the window where the frames
+ * will be inserted
+ */
+ temp = (nframes - xfer->pipe->isoc_next) & MUSB2_MASK_FRAME;
+
+ if (usb2_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) {
+ fs_frames = (xfer->nframes + 7) / 8;
+ } else {
+ fs_frames = xfer->nframes;
+ }
+
+ if ((xfer->pipe->is_synced == 0) ||
+ (temp < fs_frames)) {
+ /*
+ * 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->pipe->isoc_next = (nframes + 3) & MUSB2_MASK_FRAME;
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(2, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ temp = (xfer->pipe->isoc_next - nframes) & MUSB2_MASK_FRAME;
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ usb2_isoc_time_expand(&sc->sc_bus, nframes) + temp +
+ fs_frames;
+
+ /* compute frame number for next insertion */
+ xfer->pipe->isoc_next += fs_frames;
+
+ /* setup TDs */
+ musbotg_setup_standard_chain(xfer);
+}
+
+static void
+musbotg_device_isoc_start(struct usb2_xfer *xfer)
+{
+ /* start TD chain */
+ musbotg_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods musbotg_device_isoc_methods =
+{
+ .open = musbotg_device_isoc_open,
+ .close = musbotg_device_isoc_close,
+ .enter = musbotg_device_isoc_enter,
+ .start = musbotg_device_isoc_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg root control support
+ *------------------------------------------------------------------------*
+ * simulate a hardware HUB by handling
+ * all the necessary requests
+ *------------------------------------------------------------------------*/
+static void
+musbotg_root_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_root_ctrl_close(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_ctrl.xfer == xfer) {
+ sc->sc_root_ctrl.xfer = NULL;
+ }
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+/*
+ * USB descriptors for the virtual Root HUB:
+ */
+
+static const struct usb2_device_descriptor musbotg_devd = {
+ .bLength = sizeof(struct usb2_device_descriptor),
+ .bDescriptorType = UDESC_DEVICE,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_HSHUBSTT,
+ .bMaxPacketSize = 64,
+ .bcdDevice = {0x00, 0x01},
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .bNumConfigurations = 1,
+};
+
+static const struct usb2_device_qualifier musbotg_odevd = {
+ .bLength = sizeof(struct usb2_device_qualifier),
+ .bDescriptorType = UDESC_DEVICE_QUALIFIER,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_FSHUB,
+ .bMaxPacketSize0 = 0,
+ .bNumConfigurations = 0,
+};
+
+static const struct musbotg_config_desc musbotg_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb2_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(musbotg_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0,
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb2_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = UIPROTO_HSHUBSTT,
+ },
+
+ .endpd = {
+ .bLength = sizeof(struct usb2_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = (UE_DIR_IN | MUSBOTG_INTR_ENDPT),
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8,
+ .bInterval = 255,
+ },
+};
+
+static const struct usb2_hub_descriptor_min musbotg_hubd = {
+ .bDescLength = sizeof(musbotg_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) >> 16,
+ .bPwrOn2PwrGood = 50,
+ .bHubContrCurrent = 0,
+ .DeviceRemovable = {0}, /* port is removable */
+};
+
+#define STRING_LANG \
+ 0x09, 0x04, /* American English */
+
+#define STRING_VENDOR \
+ 'M', 0, 'e', 0, 'n', 0, 't', 0, 'o', 0, 'r', 0, ' ', 0, \
+ 'G', 0, 'r', 0, 'a', 0, 'p', 0, 'h', 0, 'i', 0, 'c', 0, 's', 0
+
+#define STRING_PRODUCT \
+ 'O', 0, 'T', 0, 'G', 0, ' ', 0, 'R', 0, \
+ 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \
+ 'U', 0, 'B', 0,
+
+USB_MAKE_STRING_DESC(STRING_LANG, musbotg_langtab);
+USB_MAKE_STRING_DESC(STRING_VENDOR, musbotg_vendor);
+USB_MAKE_STRING_DESC(STRING_PRODUCT, musbotg_product);
+
+static void
+musbotg_root_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_root_ctrl_start(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_ctrl.xfer = xfer;
+
+ usb2_bus_roothub_exec(xfer->xroot->bus);
+}
+
+static void
+musbotg_root_ctrl_task(struct usb2_bus *bus)
+{
+ musbotg_root_ctrl_poll(MUSBOTG_BUS2SC(bus));
+}
+
+static void
+musbotg_root_ctrl_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ uint16_t value;
+ uint16_t index;
+ uint8_t use_polling;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_SETUP) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ musbotg_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* buffer reset */
+ std->ptr = USB_ADD_BYTES(&sc->sc_hub_temp, 0);
+ std->len = 0;
+
+ value = UGETW(std->req.wValue);
+ index = UGETW(std->req.wIndex);
+
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ /* demultiplex the control request */
+
+ switch (std->req.bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (std->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 (std->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 (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (UGETW(std->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(std->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 (std->req.bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_INTERFACE:
+ switch (std->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 (std->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 (std->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 (std->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 (std->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 (std->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;
+ }
+ std->len = sizeof(musbotg_devd);
+ std->ptr = USB_ADD_BYTES(&musbotg_devd, 0);
+ goto tr_valid;
+ case UDESC_CONFIG:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ std->len = sizeof(musbotg_confd);
+ std->ptr = USB_ADD_BYTES(&musbotg_confd, 0);
+ goto tr_valid;
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ std->len = sizeof(musbotg_langtab);
+ std->ptr = USB_ADD_BYTES(&musbotg_langtab, 0);
+ goto tr_valid;
+
+ case 1: /* Vendor */
+ std->len = sizeof(musbotg_vendor);
+ std->ptr = USB_ADD_BYTES(&musbotg_vendor, 0);
+ goto tr_valid;
+
+ case 2: /* Product */
+ std->len = sizeof(musbotg_product);
+ std->ptr = USB_ADD_BYTES(&musbotg_product, 0);
+ goto tr_valid;
+ default:
+ break;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_stalled;
+
+tr_handle_get_config:
+ std->len = 1;
+ sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+ goto tr_valid;
+
+tr_handle_get_status:
+ std->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:
+ std->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:
+ std->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(8, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+
+ switch (value) {
+ case UHF_PORT_SUSPEND:
+ musbotg_wakeup_peer(xfer);
+ 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;
+ musbotg_pull_down(sc);
+ musbotg_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:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_set_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(8, "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:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_get_port_status:
+
+ DPRINTFN(8, "UR_GET_PORT_STATUS\n");
+
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ if (sc->sc_flags.status_vbus) {
+ musbotg_clocks_on(sc);
+ musbotg_pull_up(sc);
+ } else {
+ musbotg_pull_down(sc);
+ musbotg_clocks_off(sc);
+ }
+
+ /* Select Device Side Mode */
+ value = UPS_PORT_MODE_DEVICE;
+
+ if (sc->sc_flags.status_high_speed) {
+ value |= UPS_HIGH_SPEED;
+ }
+ 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 EP0 state */
+ sc->sc_ep0_busy = 0;
+ sc->sc_ep0_cmd = 0;
+ }
+ }
+ if (sc->sc_flags.change_suspend) {
+ value |= UPS_C_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortChange, value);
+ std->len = sizeof(sc->sc_hub_temp.ps);
+ goto tr_valid;
+
+tr_handle_get_class_descriptor:
+ if (value & 0xFF) {
+ goto tr_stalled;
+ }
+ std->ptr = USB_ADD_BYTES(&musbotg_hubd, 0);
+ std->len = sizeof(musbotg_hubd);
+ goto tr_valid;
+
+tr_stalled:
+ std->err = USB_ERR_STALLED;
+tr_valid:
+done:
+ return;
+}
+
+static void
+musbotg_root_ctrl_poll(struct musbotg_softc *sc)
+{
+ usb2_sw_transfer(&sc->sc_root_ctrl,
+ &musbotg_root_ctrl_done);
+}
+
+struct usb2_pipe_methods musbotg_root_ctrl_methods =
+{
+ .open = musbotg_root_ctrl_open,
+ .close = musbotg_root_ctrl_close,
+ .enter = musbotg_root_ctrl_enter,
+ .start = musbotg_root_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 0,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg root interrupt support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_root_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_root_intr_close(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_intr.xfer == xfer) {
+ sc->sc_root_intr.xfer = NULL;
+ }
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_root_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_root_intr_start(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_intr.xfer = xfer;
+}
+
+struct usb2_pipe_methods musbotg_root_intr_methods =
+{
+ .open = musbotg_root_intr_open,
+ .close = musbotg_root_intr_close,
+ .enter = musbotg_root_intr_enter,
+ .start = musbotg_root_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+static void
+musbotg_xfer_setup(struct usb2_setup_params *parm)
+{
+ const struct usb2_hw_ep_profile *pf;
+ struct musbotg_softc *sc;
+ struct usb2_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t n;
+ uint8_t ep_no;
+
+ sc = MUSBOTG_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 = 0x400;
+ parm->hc_max_frame_size = 0x400;
+
+ if ((parm->methods == &musbotg_device_isoc_methods) ||
+ (parm->methods == &musbotg_device_intr_methods))
+ parm->hc_max_packet_count = 3;
+ else
+ parm->hc_max_packet_count = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ /*
+ * compute maximum number of TDs
+ */
+ if (parm->methods == &musbotg_device_ctrl_methods) {
+
+ ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &musbotg_device_bulk_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &musbotg_device_intr_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &musbotg_device_isoc_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else {
+
+ ntd = 0;
+ }
+
+ /*
+ * check if "usb2_transfer_setup_sub" set an error
+ */
+ if (parm->err) {
+ return;
+ }
+ /*
+ * allocate transfer descriptors
+ */
+ last_obj = NULL;
+
+ /*
+ * get profile stuff
+ */
+ if (ntd) {
+
+ ep_no = xfer->endpoint & UE_ADDR;
+ musbotg_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 musbotg_td *td;
+
+ if (parm->buf) {
+
+ td = USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+ /* init TD */
+ td->max_frame_size = xfer->max_frame_size;
+ td->ep_no = ep_no;
+ td->obj_next = last_obj;
+
+ last_obj = td;
+ }
+ parm->size[0] += sizeof(*td);
+ }
+
+ xfer->td_start[0] = last_obj;
+}
+
+static void
+musbotg_xfer_unsetup(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc,
+ struct usb2_pipe *pipe)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ pipe, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb2_mode,
+ sc->sc_rt_addr);
+
+ if (udev->device_index == sc->sc_rt_addr) {
+
+ if (udev->flags.usb2_mode != USB_MODE_HOST) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bEndpointAddress) {
+ case USB_CONTROL_ENDPOINT:
+ pipe->methods = &musbotg_root_ctrl_methods;
+ break;
+ case UE_DIR_IN | MUSBOTG_INTR_ENDPT:
+ pipe->methods = &musbotg_root_intr_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ } else {
+
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ if ((udev->speed != USB_SPEED_FULL) &&
+ (udev->speed != USB_SPEED_HIGH)) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ pipe->methods = &musbotg_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ pipe->methods = &musbotg_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ pipe->methods = &musbotg_device_isoc_methods;
+ break;
+ case UE_BULK:
+ pipe->methods = &musbotg_device_bulk_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+struct usb2_bus_methods musbotg_bus_methods =
+{
+ .pipe_init = &musbotg_pipe_init,
+ .xfer_setup = &musbotg_xfer_setup,
+ .xfer_unsetup = &musbotg_xfer_unsetup,
+ .do_poll = &musbotg_do_poll,
+ .get_hw_ep_profile = &musbotg_get_hw_ep_profile,
+ .set_stall = &musbotg_set_stall,
+ .clear_stall = &musbotg_clear_stall,
+ .roothub_exec = &musbotg_root_ctrl_task,
+};
diff --git a/sys/dev/usb/controller/musb_otg.h b/sys/dev/usb/controller/musb_otg.h
new file mode 100644
index 000000000000..0d880e1fd4dc
--- /dev/null
+++ b/sys/dev/usb/controller/musb_otg.h
@@ -0,0 +1,407 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 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 header file defines the registers of the Mentor Graphics
+ * USB OnTheGo Inventra chip.
+ */
+
+#ifndef _MUSB2_OTG_H_
+#define _MUSB2_OTG_H_
+
+#define MUSB2_MAX_DEVICES (USB_MIN_DEVICES + 1)
+
+/* Common registers */
+
+#define MUSB2_REG_FADDR 0x0000 /* function address register */
+#define MUSB2_MASK_FADDR 0x7F
+
+#define MUSB2_REG_POWER 0x0001 /* power register */
+#define MUSB2_MASK_SUSPM_ENA 0x01
+#define MUSB2_MASK_SUSPMODE 0x02
+#define MUSB2_MASK_RESUME 0x04
+#define MUSB2_MASK_RESET 0x08
+#define MUSB2_MASK_HSMODE 0x10
+#define MUSB2_MASK_HSENAB 0x20
+#define MUSB2_MASK_SOFTC 0x40
+#define MUSB2_MASK_ISOUPD 0x80
+
+/* Endpoint interrupt handling */
+
+#define MUSB2_REG_INTTX 0x0002 /* transmit interrupt register */
+#define MUSB2_REG_INTRX 0x0004 /* receive interrupt register */
+#define MUSB2_REG_INTTXE 0x0006 /* transmit interrupt enable register */
+#define MUSB2_REG_INTRXE 0x0008 /* receive interrupt enable register */
+#define MUSB2_MASK_EPINT(epn) (1 << (epn)) /* epn = [0..15] */
+
+/* Common interrupt handling */
+
+#define MUSB2_REG_INTUSB 0x000A /* USB interrupt register */
+#define MUSB2_MASK_ISUSP 0x01
+#define MUSB2_MASK_IRESUME 0x02
+#define MUSB2_MASK_IRESET 0x04
+#define MUSB2_MASK_IBABBLE 0x04
+#define MUSB2_MASK_ISOF 0x08
+#define MUSB2_MASK_ICONN 0x10
+#define MUSB2_MASK_IDISC 0x20
+#define MUSB2_MASK_ISESSRQ 0x40
+#define MUSB2_MASK_IVBUSERR 0x80
+
+#define MUSB2_REG_INTUSBE 0x000B /* USB interrupt enable register */
+#define MUSB2_REG_FRAME 0x000C /* USB frame register */
+#define MUSB2_MASK_FRAME 0x3FF /* 0..1023 */
+
+#define MUSB2_REG_EPINDEX 0x000E /* endpoint index register */
+#define MUSB2_MASK_EPINDEX 0x0F
+
+#define MUSB2_REG_TESTMODE 0x000F /* test mode register */
+#define MUSB2_MASK_TSE0_NAK 0x01
+#define MUSB2_MASK_TJ 0x02
+#define MUSB2_MASK_TK 0x04
+#define MUSB2_MASK_TPACKET 0x08
+#define MUSB2_MASK_TFORCE_HS 0x10
+#define MUSB2_MASK_TFORCE_LS 0x20
+#define MUSB2_MASK_TFIFO_ACC 0x40
+#define MUSB2_MASK_TFORCE_HC 0x80
+
+#define MUSB2_REG_INDEXED_CSR 0x0010 /* EP control status register offset */
+
+#define MUSB2_REG_TXMAXP (0x0000 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_REG_RXMAXP (0x0004 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_PKTSIZE 0x03FF /* in bytes, should be even */
+#define MUSB2_MASK_PKTMULT 0xFC00 /* HS packet multiplier: 0..2 */
+
+#define MUSB2_REG_TXCSRL (0x0002 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_CSRL_TXPKTRDY 0x01
+#define MUSB2_MASK_CSRL_TXFIFONEMPTY 0x02
+#define MUSB2_MASK_CSRL_TXUNDERRUN 0x04 /* Device Mode */
+#define MUSB2_MASK_CSRL_TXERROR 0x04 /* Host Mode */
+#define MUSB2_MASK_CSRL_TXFFLUSH 0x08
+#define MUSB2_MASK_CSRL_TXSENDSTALL 0x10/* Device Mode */
+#define MUSB2_MASK_CSRL_TXSETUPPKT 0x10 /* Host Mode */
+#define MUSB2_MASK_CSRL_TXSENTSTALL 0x20/* Device Mode */
+#define MUSB2_MASK_CSRL_TXSTALLED 0x20 /* Host Mode */
+#define MUSB2_MASK_CSRL_TXDT_CLR 0x40
+#define MUSB2_MASK_CSRL_TXINCOMP 0x80
+
+/* Device Side Mode */
+#define MUSB2_MASK_CSR0L_RXPKTRDY 0x01
+#define MUSB2_MASK_CSR0L_TXPKTRDY 0x02
+#define MUSB2_MASK_CSR0L_SENTSTALL 0x04
+#define MUSB2_MASK_CSR0L_DATAEND 0x08
+#define MUSB2_MASK_CSR0L_SETUPEND 0x10
+#define MUSB2_MASK_CSR0L_SENDSTALL 0x20
+#define MUSB2_MASK_CSR0L_RXPKTRDY_CLR 0x40
+#define MUSB2_MASK_CSR0L_SETUPEND_CLR 0x80
+
+/* Host Side Mode */
+#define MUSB2_MASK_CSR0L_RXSTALL 0x04
+#define MUSB2_MASK_CSR0L_SETUPPKT 0x08
+#define MUSB2_MASK_CSR0L_ERROR 0x10
+#define MUSB2_MASK_CSR0L_REQPKT 0x20
+#define MUSB2_MASK_CSR0L_STATUSPKT 0x40
+#define MUSB2_MASK_CSR0L_NAKTIMO 0x80
+
+#define MUSB2_REG_TXCSRH (0x0003 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_CSRH_TXDT_VAL 0x01 /* Host Mode */
+#define MUSB2_MASK_CSRH_TXDT_WR 0x02 /* Host Mode */
+#define MUSB2_MASK_CSRH_TXDMAREQMODE 0x04
+#define MUSB2_MASK_CSRH_TXDT_SWITCH 0x08
+#define MUSB2_MASK_CSRH_TXDMAREQENA 0x10
+#define MUSB2_MASK_CSRH_RXMODE 0x00
+#define MUSB2_MASK_CSRH_TXMODE 0x20
+#define MUSB2_MASK_CSRH_TXISO 0x40 /* Device Mode */
+#define MUSB2_MASK_CSRH_TXAUTOSET 0x80
+
+#define MUSB2_MASK_CSR0H_FFLUSH 0x01 /* Device Side flush FIFO */
+#define MUSB2_MASK_CSR0H_DT 0x02 /* Host Side data toggle */
+#define MUSB2_MASK_CSR0H_DT_SET 0x04 /* Host Side */
+#define MUSB2_MASK_CSR0H_PING_DIS 0x08 /* Host Side */
+
+#define MUSB2_REG_RXCSRL (0x0006 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_CSRL_RXPKTRDY 0x01
+#define MUSB2_MASK_CSRL_RXFIFOFULL 0x02
+#define MUSB2_MASK_CSRL_RXOVERRUN 0x04
+#define MUSB2_MASK_CSRL_RXDATAERR 0x08
+#define MUSB2_MASK_CSRL_RXFFLUSH 0x10
+#define MUSB2_MASK_CSRL_RXSENDSTALL 0x20/* Device Mode */
+#define MUSB2_MASK_CSRL_RXREQPKT 0x20 /* Host Mode */
+#define MUSB2_MASK_CSRL_RXSENTSTALL 0x40/* Device Mode */
+#define MUSB2_MASK_CSRL_RXSTALL 0x40 /* Host Mode */
+#define MUSB2_MASK_CSRL_RXDT_CLR 0x80
+
+#define MUSB2_REG_RXCSRH (0x0007 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_CSRH_RXINCOMP 0x01
+#define MUSB2_MASK_CSRH_RXDT_VAL 0x02 /* Host Mode */
+#define MUSB2_MASK_CSRH_RXDT_SET 0x04 /* Host Mode */
+#define MUSB2_MASK_CSRH_RXDMAREQMODE 0x08
+#define MUSB2_MASK_CSRH_RXNYET 0x10
+#define MUSB2_MASK_CSRH_RXDMAREQENA 0x20
+#define MUSB2_MASK_CSRH_RXISO 0x40 /* Device Mode */
+#define MUSB2_MASK_CSRH_RXAUTOREQ 0x40 /* Host Mode */
+#define MUSB2_MASK_CSRH_RXAUTOCLEAR 0x80
+
+#define MUSB2_REG_RXCOUNT (0x0008 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_RXCOUNT 0xFFFF
+
+#define MUSB2_REG_TXTI (0x000A + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_REG_RXTI (0x000C + MUSB2_REG_INDEXED_CSR)
+
+/* Host Mode */
+#define MUSB2_MASK_TI_SPEED 0xC0
+#define MUSB2_MASK_TI_SPEED_LO 0xC0
+#define MUSB2_MASK_TI_SPEED_FS 0x80
+#define MUSB2_MASK_TI_SPEED_HS 0x40
+#define MUSB2_MASK_TI_PROTO_CTRL 0x00
+#define MUSB2_MASK_TI_PROTO_ISOC 0x10
+#define MUSB2_MASK_TI_PROTO_BULK 0x20
+#define MUSB2_MASK_TI_PROTO_INTR 0x30
+#define MUSB2_MASK_TI_EP_NUM 0x0F
+
+#define MUSB2_REG_TXNAKLIMIT (0x000B /* EPN=0 */ + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_REG_RXNAKLIMIT (0x000D /* EPN=0 */ + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_NAKLIMIT 0xFF
+
+#define MUSB2_REG_FSIZE (0x000F + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_RX_FSIZE 0xF0 /* 3..13, 2**n bytes */
+#define MUSB2_MASK_TX_FSIZE 0x0F /* 3..13, 2**n bytes */
+
+#define MUSB2_REG_EPFIFO(n) (0x0020 + (4*(n)))
+
+#define MUSB2_REG_CONFDATA 0x000F /* EPN=0 */
+#define MUSB2_MASK_CD_UTMI_DW 0x01
+#define MUSB2_MASK_CD_SOFTCONE 0x02
+#define MUSB2_MASK_CD_DYNFIFOSZ 0x04
+#define MUSB2_MASK_CD_HBTXE 0x08
+#define MUSB2_MASK_CD_HBRXE 0x10
+#define MUSB2_MASK_CD_BIGEND 0x20
+#define MUSB2_MASK_CD_MPTXE 0x40
+#define MUSB2_MASK_CD_MPRXE 0x80
+
+/* Various registers */
+
+#define MUSB2_REG_DEVCTL 0x0060
+#define MUSB2_MASK_SESS 0x01
+#define MUSB2_MASK_HOSTREQ 0x02
+#define MUSB2_MASK_HOSTMD 0x04
+#define MUSB2_MASK_VBUS0 0x08
+#define MUSB2_MASK_VBUS1 0x10
+#define MUSB2_MASK_LSDEV 0x20
+#define MUSB2_MASK_FSDEV 0x40
+#define MUSB2_MASK_BDEV 0x80
+
+#define MUSB2_REG_MISC 0x0061
+#define MUSB2_MASK_RXEDMA 0x01
+#define MUSB2_MASK_TXEDMA 0x02
+
+#define MUSB2_REG_TXFIFOSZ 0x0062
+#define MUSB2_REG_RXFIFOSZ 0x0063
+#define MUSB2_MASK_FIFODB 0x10 /* set if double buffering, r/w */
+#define MUSB2_MASK_FIFOSZ 0x0F
+#define MUSB2_VAL_FIFOSZ_8 0
+#define MUSB2_VAL_FIFOSZ_16 1
+#define MUSB2_VAL_FIFOSZ_32 2
+#define MUSB2_VAL_FIFOSZ_64 3
+#define MUSB2_VAL_FIFOSZ_128 4
+#define MUSB2_VAL_FIFOSZ_256 5
+#define MUSB2_VAL_FIFOSZ_512 6
+#define MUSB2_VAL_FIFOSZ_1024 7
+#define MUSB2_VAL_FIFOSZ_2048 8
+#define MUSB2_VAL_FIFOSZ_4096 9
+
+#define MUSB2_REG_TXFIFOADD 0x0064
+#define MUSB2_REG_RXFIFOADD 0x0066
+#define MUSB2_MASK_FIFOADD 0xFFF /* unit is 8-bytes */
+
+#define MUSB2_REG_VSTATUS 0x0068
+#define MUSB2_REG_VCONTROL 0x0068
+#define MUSB2_REG_HWVERS 0x006C
+#define MUSB2_REG_ULPI_BASE 0x0070
+
+#define MUSB2_REG_EPINFO 0x0078
+#define MUSB2_MASK_NRXEP 0xF0
+#define MUSB2_MASK_NTXEP 0x0F
+
+#define MUSB2_REG_RAMINFO 0x0079
+#define MUSB2_REG_LINKINFO 0x007A
+
+#define MUSB2_REG_VPLEN 0x007B
+#define MUSB2_MASK_VPLEN 0xFF
+
+#define MUSB2_REG_HS_EOF1 0x007C
+#define MUSB2_REG_FS_EOF1 0x007D
+#define MUSB2_REG_LS_EOF1 0x007E
+#define MUSB2_REG_SOFT_RST 0x007F
+#define MUSB2_MASK_SRST 0x01
+#define MUSB2_MASK_SRSTX 0x02
+
+#define MUSB2_REG_RQPKTCOUNT(n) (0x0300 + (4*(n))
+#define MUSB2_REG_RXDBDIS 0x0340
+#define MUSB2_REG_TXDBDIS 0x0342
+#define MUSB2_MASK_DB(n) (1 << (n)) /* disable double buffer, n = [0..15] */
+
+#define MUSB2_REG_CHIRPTO 0x0344
+#define MUSB2_REG_HSRESUM 0x0346
+
+/* Host Mode only registers */
+
+#define MUSB2_REG_TXFADDR(n) (0x0080 + (8*(n)))
+#define MUSB2_REG_TXHADDR(n) (0x0082 + (8*(n)))
+#define MUSB2_REG_TXHUBPORT(n) (0x0083 + (8*(n)))
+#define MUSB2_REG_RXFADDR(n) (0x0084 + (8*(n)))
+#define MUSB2_REG_RXHADDR(n) (0x0086 + (8*(n)))
+#define MUSB2_REG_RXHPORT(n) (0x0087 + (8*(n)))
+
+#define MUSB2_EP_MAX 16 /* maximum number of endpoints */
+
+#define MUSB2_READ_2(sc, reg) \
+ bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define MUSB2_WRITE_2(sc, reg, data) \
+ bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+#define MUSB2_READ_1(sc, reg) \
+ bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define MUSB2_WRITE_1(sc, reg, data) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+struct musbotg_td;
+struct musbotg_softc;
+
+typedef uint8_t (musbotg_cmd_t)(struct musbotg_td *td);
+
+struct musbotg_dma {
+ struct musbotg_softc *sc;
+ uint32_t dma_chan;
+ uint8_t busy:1;
+ uint8_t complete:1;
+ uint8_t error:1;
+};
+
+struct musbotg_td {
+ struct musbotg_td *obj_next;
+ musbotg_cmd_t *func;
+ struct usb2_page_cache *pc;
+ uint32_t offset;
+ uint32_t remainder;
+ uint16_t max_frame_size; /* packet_size * mult */
+ uint8_t ep_no;
+ 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 dma_enabled:1;
+};
+
+struct musbotg_std_temp {
+ musbotg_cmd_t *func;
+ struct usb2_page_cache *pc;
+ struct musbotg_td *td;
+ struct musbotg_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;
+};
+
+struct musbotg_config_desc {
+ struct usb2_config_descriptor confd;
+ struct usb2_interface_descriptor ifcd;
+ struct usb2_endpoint_descriptor endpd;
+} __packed;
+
+union musbotg_hub_temp {
+ uWord wValue;
+ struct usb2_port_status ps;
+};
+
+struct musbotg_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 status_high_speed:1; /* set if High Speed is selected */
+ 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 musbotg_softc {
+ struct usb2_bus sc_bus;
+ union musbotg_hub_temp sc_hub_temp;
+ struct usb2_sw_transfer sc_root_ctrl;
+ struct usb2_sw_transfer sc_root_intr;
+ struct usb2_hw_ep_profile sc_hw_ep_profile[16];
+
+ struct usb2_device *sc_devices[MUSB2_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;
+
+ uint32_t sc_bounce_buf[(1024 * 3) / 4]; /* bounce buffer */
+
+ uint8_t sc_ep_max; /* maximum number of RX and TX
+ * endpoints supported */
+ 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_ep0_busy; /* set if ep0 is busy */
+ uint8_t sc_ep0_cmd; /* pending commands */
+ uint8_t sc_conf_data; /* copy of hardware register */
+
+ uint8_t sc_hub_idata[1];
+
+ struct musbotg_flags sc_flags;
+};
+
+/* prototypes */
+
+usb2_error_t musbotg_init(struct musbotg_softc *sc);
+void musbotg_uninit(struct musbotg_softc *sc);
+void musbotg_suspend(struct musbotg_softc *sc);
+void musbotg_resume(struct musbotg_softc *sc);
+void musbotg_interrupt(struct musbotg_softc *sc);
+void musbotg_vbus_interrupt(struct musbotg_softc *sc, uint8_t is_on);
+
+#endif /* _MUSB2_OTG_H_ */
diff --git a/sys/dev/usb/controller/musb_otg_atmelarm.c b/sys/dev/usb/controller/musb_otg_atmelarm.c
new file mode 100644
index 000000000000..76524246a9ee
--- /dev/null
+++ b/sys/dev/usb/controller/musb_otg_atmelarm.c
@@ -0,0 +1,239 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 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 <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/mus2_otg.h>
+
+#include <sys/rman.h>
+
+static device_probe_t musbotg_probe;
+static device_attach_t musbotg_attach;
+static device_detach_t musbotg_detach;
+static device_shutdown_t musbotg_shutdown;
+
+struct musbotg_super_softc {
+ struct musbotg_softc sc_otg; /* must be first */
+};
+
+static void
+musbotg_vbus_poll(struct musbotg_super_softc *sc)
+{
+ uint8_t vbus_val = 1; /* fake VBUS on - TODO */
+
+ /* just forward it */
+ musbotg_vbus_interrupt(&sc->sc_otg, vbus_val);
+}
+
+static void
+musbotg_clocks_on(void *arg)
+{
+#if 0
+ struct musbotg_super_softc *sc = arg;
+
+#endif
+}
+
+static void
+musbotg_clocks_off(void *arg)
+{
+#if 0
+ struct musbotg_super_softc *sc = arg;
+
+#endif
+}
+
+static int
+musbotg_probe(device_t dev)
+{
+ device_set_desc(dev, "MUSB OTG integrated USB controller");
+ return (0);
+}
+
+static int
+musbotg_attach(device_t dev)
+{
+ struct musbotg_super_softc *sc = device_get_softc(dev);
+ int err;
+ int rid;
+
+ /* setup MUSB OTG USB controller interface softc */
+ sc->sc_otg.sc_clocks_on = &musbotg_clocks_on;
+ sc->sc_otg.sc_clocks_off = &musbotg_clocks_off;
+ sc->sc_otg.sc_clocks_arg = sc;
+
+ /* 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 = MUSB2_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_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);
+ sc->sc_otg.sc_io_size = rman_get_size(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);
+
+#if (__FreeBSD_version >= 700031)
+ err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)musbotg_interrupt, sc, &sc->sc_otg.sc_intr_hdl);
+#else
+ err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ (void *)musbotg_interrupt, sc, &sc->sc_otg.sc_intr_hdl);
+#endif
+ if (err) {
+ sc->sc_otg.sc_intr_hdl = NULL;
+ goto error;
+ }
+ err = musbotg_init(&sc->sc_otg);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_otg.sc_bus.bdev);
+ }
+ if (err) {
+ goto error;
+ } else {
+ /* poll VBUS one time */
+ musbotg_vbus_poll(sc);
+ }
+ return (0);
+
+error:
+ musbotg_detach(dev);
+ return (ENXIO);
+}
+
+static int
+musbotg_detach(device_t dev)
+{
+ struct musbotg_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_all_children(dev);
+
+ if (sc->sc_otg.sc_irq_res && sc->sc_otg.sc_intr_hdl) {
+ /*
+ * only call musbotg_uninit() after musbotg_init()
+ */
+ musbotg_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;
+ }
+ usb2_bus_mem_free_all(&sc->sc_otg.sc_bus, NULL);
+
+ return (0);
+}
+
+static int
+musbotg_shutdown(device_t dev)
+{
+ struct musbotg_super_softc *sc = device_get_softc(dev);
+ int err;
+
+ err = bus_generic_shutdown(dev);
+ if (err)
+ return (err);
+
+ musbotg_uninit(&sc->sc_otg);
+
+ return (0);
+}
+
+static device_method_t musbotg_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, musbotg_probe),
+ DEVMETHOD(device_attach, musbotg_attach),
+ DEVMETHOD(device_detach, musbotg_detach),
+ DEVMETHOD(device_shutdown, musbotg_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+};
+
+static driver_t musbotg_driver = {
+ "musbotg",
+ musbotg_methods,
+ sizeof(struct musbotg_super_softc),
+};
+
+static devclass_t musbotg_devclass;
+
+DRIVER_MODULE(musbotg, atmelarm, musbotg_driver, musbotg_devclass, 0, 0);
+MODULE_DEPEND(musbotg, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ohci.c b/sys/dev/usb/controller/ohci.c
new file mode 100644
index 000000000000..7751bd57f4f9
--- /dev/null
+++ b/sys/dev/usb/controller/ohci.c
@@ -0,0 +1,2862 @@
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. 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$");
+
+/*
+ * USB Open Host Controller driver.
+ *
+ * OHCI spec: http://www.compaq.com/productinfo/development/openhci.html
+ * USB spec: http://www.usb.org/developers/docs/usbspec.zip
+ */
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.h>
+
+#define USB_DEBUG_VAR ohcidebug
+
+#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_sw_transfer.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/ohci.h>
+
+#define OHCI_BUS2SC(bus) ((ohci_softc_t *)(((uint8_t *)(bus)) - \
+ USB_P2U(&(((ohci_softc_t *)0)->sc_bus))))
+
+#if USB_DEBUG
+static int ohcidebug = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, ohci, CTLFLAG_RW, 0, "USB ohci");
+SYSCTL_INT(_hw_usb2_ohci, OID_AUTO, debug, CTLFLAG_RW,
+ &ohcidebug, 0, "ohci debug level");
+static void ohci_dumpregs(ohci_softc_t *);
+static void ohci_dump_tds(ohci_td_t *);
+static uint8_t ohci_dump_td(ohci_td_t *);
+static void ohci_dump_ed(ohci_ed_t *);
+static uint8_t ohci_dump_itd(ohci_itd_t *);
+static void ohci_dump_itds(ohci_itd_t *);
+
+#endif
+
+#define OBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \
+ BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE)
+#define OWRITE1(sc, r, x) \
+ do { OBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0)
+#define OWRITE2(sc, r, x) \
+ do { OBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0)
+#define OWRITE4(sc, r, x) \
+ do { OBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0)
+#define OREAD1(sc, r) (OBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+#define OREAD2(sc, r) (OBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+#define OREAD4(sc, r) (OBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+
+#define OHCI_INTR_ENDPT 1
+
+extern struct usb2_bus_methods ohci_bus_methods;
+extern struct usb2_pipe_methods ohci_device_bulk_methods;
+extern struct usb2_pipe_methods ohci_device_ctrl_methods;
+extern struct usb2_pipe_methods ohci_device_intr_methods;
+extern struct usb2_pipe_methods ohci_device_isoc_methods;
+extern struct usb2_pipe_methods ohci_root_ctrl_methods;
+extern struct usb2_pipe_methods ohci_root_intr_methods;
+
+static void ohci_root_ctrl_poll(struct ohci_softc *sc);
+static void ohci_do_poll(struct usb2_bus *bus);
+static void ohci_device_done(struct usb2_xfer *xfer, usb2_error_t error);
+
+static usb2_sw_transfer_func_t ohci_root_intr_done;
+static usb2_sw_transfer_func_t ohci_root_ctrl_done;
+static void ohci_timeout(void *arg);
+static uint8_t ohci_check_transfer(struct usb2_xfer *xfer);
+
+struct ohci_std_temp {
+ struct usb2_page_cache *pc;
+ ohci_td_t *td;
+ ohci_td_t *td_next;
+ uint32_t average;
+ uint32_t td_flags;
+ uint32_t len;
+ uint16_t max_frame_size;
+ uint8_t shortpkt;
+ uint8_t setup_alt_next;
+ uint8_t short_frames_ok;
+};
+
+static struct ohci_hcca *
+ohci_get_hcca(ohci_softc_t *sc)
+{
+ usb2_pc_cpu_invalidate(&sc->sc_hw.hcca_pc);
+ return (sc->sc_hcca_p);
+}
+
+void
+ohci_iterate_hw_softc(struct usb2_bus *bus, usb2_bus_mem_sub_cb_t *cb)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(bus);
+ uint32_t i;
+
+ cb(bus, &sc->sc_hw.hcca_pc, &sc->sc_hw.hcca_pg,
+ sizeof(ohci_hcca_t), OHCI_HCCA_ALIGN);
+
+ cb(bus, &sc->sc_hw.ctrl_start_pc, &sc->sc_hw.ctrl_start_pg,
+ sizeof(ohci_ed_t), OHCI_ED_ALIGN);
+
+ cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg,
+ sizeof(ohci_ed_t), OHCI_ED_ALIGN);
+
+ cb(bus, &sc->sc_hw.isoc_start_pc, &sc->sc_hw.isoc_start_pg,
+ sizeof(ohci_ed_t), OHCI_ED_ALIGN);
+
+ for (i = 0; i != OHCI_NO_EDS; i++) {
+ cb(bus, sc->sc_hw.intr_start_pc + i, sc->sc_hw.intr_start_pg + i,
+ sizeof(ohci_ed_t), OHCI_ED_ALIGN);
+ }
+}
+
+static usb2_error_t
+ohci_controller_init(ohci_softc_t *sc)
+{
+ struct usb2_page_search buf_res;
+ uint32_t i;
+ uint32_t ctl;
+ uint32_t ival;
+ uint32_t hcr;
+ uint32_t fm;
+ uint32_t per;
+ uint32_t desca;
+
+ /* Determine in what context we are running. */
+ ctl = OREAD4(sc, OHCI_CONTROL);
+ if (ctl & OHCI_IR) {
+ /* SMM active, request change */
+ DPRINTF("SMM active, request owner change\n");
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_OCR);
+ for (i = 0; (i < 100) && (ctl & OHCI_IR); i++) {
+ usb2_pause_mtx(NULL, hz / 1000);
+ ctl = OREAD4(sc, OHCI_CONTROL);
+ }
+ if (ctl & OHCI_IR) {
+ device_printf(sc->sc_bus.bdev,
+ "SMM does not respond, resetting\n");
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+ goto reset;
+ }
+ } else {
+ DPRINTF("cold started\n");
+reset:
+ /* controller was cold started */
+ usb2_pause_mtx(NULL,
+ USB_MS_TO_TICKS(USB_BUS_RESET_DELAY));
+ }
+
+ /*
+ * This reset should not be necessary according to the OHCI spec, but
+ * without it some controllers do not start.
+ */
+ DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev));
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+
+ usb2_pause_mtx(NULL,
+ USB_MS_TO_TICKS(USB_BUS_RESET_DELAY));
+
+ /* we now own the host controller and the bus has been reset */
+ ival = OHCI_GET_IVAL(OREAD4(sc, OHCI_FM_INTERVAL));
+
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_HCR); /* Reset HC */
+ /* nominal time for a reset is 10 us */
+ for (i = 0; i < 10; i++) {
+ DELAY(10);
+ hcr = OREAD4(sc, OHCI_COMMAND_STATUS) & OHCI_HCR;
+ if (!hcr) {
+ break;
+ }
+ }
+ if (hcr) {
+ device_printf(sc->sc_bus.bdev, "reset timeout\n");
+ return (USB_ERR_IOERROR);
+ }
+#if USB_DEBUG
+ if (ohcidebug > 15) {
+ ohci_dumpregs(sc);
+ }
+#endif
+
+ /* The controller is now in SUSPEND state, we have 2ms to finish. */
+
+ /* set up HC registers */
+ usb2_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res);
+ OWRITE4(sc, OHCI_HCCA, buf_res.physaddr);
+
+ usb2_get_page(&sc->sc_hw.ctrl_start_pc, 0, &buf_res);
+ OWRITE4(sc, OHCI_CONTROL_HEAD_ED, buf_res.physaddr);
+
+ usb2_get_page(&sc->sc_hw.bulk_start_pc, 0, &buf_res);
+ OWRITE4(sc, OHCI_BULK_HEAD_ED, buf_res.physaddr);
+
+ /* disable all interrupts and then switch on all desired interrupts */
+ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS);
+ OWRITE4(sc, OHCI_INTERRUPT_ENABLE, sc->sc_eintrs | OHCI_MIE);
+ /* switch on desired functional features */
+ ctl = OREAD4(sc, OHCI_CONTROL);
+ ctl &= ~(OHCI_CBSR_MASK | OHCI_LES | OHCI_HCFS_MASK | OHCI_IR);
+ ctl |= OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE |
+ OHCI_RATIO_1_4 | OHCI_HCFS_OPERATIONAL;
+ /* And finally start it! */
+ OWRITE4(sc, OHCI_CONTROL, ctl);
+
+ /*
+ * The controller is now OPERATIONAL. Set a some final
+ * registers that should be set earlier, but that the
+ * controller ignores when in the SUSPEND state.
+ */
+ fm = (OREAD4(sc, OHCI_FM_INTERVAL) & OHCI_FIT) ^ OHCI_FIT;
+ fm |= OHCI_FSMPS(ival) | ival;
+ OWRITE4(sc, OHCI_FM_INTERVAL, fm);
+ per = OHCI_PERIODIC(ival); /* 90% periodic */
+ OWRITE4(sc, OHCI_PERIODIC_START, per);
+
+ /* Fiddle the No OverCurrent Protection bit to avoid chip bug. */
+ desca = OREAD4(sc, OHCI_RH_DESCRIPTOR_A);
+ OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca | OHCI_NOCP);
+ OWRITE4(sc, OHCI_RH_STATUS, OHCI_LPSC); /* Enable port power */
+ usb2_pause_mtx(NULL,
+ USB_MS_TO_TICKS(OHCI_ENABLE_POWER_DELAY));
+ OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca);
+
+ /*
+ * The AMD756 requires a delay before re-reading the register,
+ * otherwise it will occasionally report 0 ports.
+ */
+ sc->sc_noport = 0;
+ for (i = 0; (i < 10) && (sc->sc_noport == 0); i++) {
+ usb2_pause_mtx(NULL,
+ USB_MS_TO_TICKS(OHCI_READ_DESC_DELAY));
+ sc->sc_noport = OHCI_GET_NDP(OREAD4(sc, OHCI_RH_DESCRIPTOR_A));
+ }
+
+#if USB_DEBUG
+ if (ohcidebug > 5) {
+ ohci_dumpregs(sc);
+ }
+#endif
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static struct ohci_ed *
+ohci_init_ed(struct usb2_page_cache *pc)
+{
+ struct usb2_page_search buf_res;
+ struct ohci_ed *ed;
+
+ usb2_get_page(pc, 0, &buf_res);
+
+ ed = buf_res.buffer;
+
+ ed->ed_self = htole32(buf_res.physaddr);
+ ed->ed_flags = htole32(OHCI_ED_SKIP);
+ ed->page_cache = pc;
+
+ return (ed);
+}
+
+usb2_error_t
+ohci_init(ohci_softc_t *sc)
+{
+ struct usb2_page_search buf_res;
+ uint16_t i;
+ uint16_t bit;
+ uint16_t x;
+ uint16_t y;
+
+ DPRINTF("start\n");
+
+ sc->sc_eintrs = OHCI_NORMAL_INTRS;
+
+ /*
+ * Setup all ED's
+ */
+
+ sc->sc_ctrl_p_last =
+ ohci_init_ed(&sc->sc_hw.ctrl_start_pc);
+
+ sc->sc_bulk_p_last =
+ ohci_init_ed(&sc->sc_hw.bulk_start_pc);
+
+ sc->sc_isoc_p_last =
+ ohci_init_ed(&sc->sc_hw.isoc_start_pc);
+
+ for (i = 0; i != OHCI_NO_EDS; i++) {
+ sc->sc_intr_p_last[i] =
+ ohci_init_ed(sc->sc_hw.intr_start_pc + i);
+ }
+
+ /*
+ * the QHs are arranged to give poll intervals that are
+ * powers of 2 times 1ms
+ */
+ bit = OHCI_NO_EDS / 2;
+ while (bit) {
+ x = bit;
+ while (x & bit) {
+ ohci_ed_t *ed_x;
+ ohci_ed_t *ed_y;
+
+ y = (x ^ bit) | (bit / 2);
+
+ /*
+ * the next QH has half the poll interval
+ */
+ ed_x = sc->sc_intr_p_last[x];
+ ed_y = sc->sc_intr_p_last[y];
+
+ ed_x->next = NULL;
+ ed_x->ed_next = ed_y->ed_self;
+
+ x++;
+ }
+ bit >>= 1;
+ }
+
+ if (1) {
+
+ ohci_ed_t *ed_int;
+ ohci_ed_t *ed_isc;
+
+ ed_int = sc->sc_intr_p_last[0];
+ ed_isc = sc->sc_isoc_p_last;
+
+ /* the last (1ms) QH */
+ ed_int->next = ed_isc;
+ ed_int->ed_next = ed_isc->ed_self;
+ }
+ usb2_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res);
+
+ sc->sc_hcca_p = buf_res.buffer;
+
+ /*
+ * Fill HCCA interrupt table. The bit reversal is to get
+ * the tree set up properly to spread the interrupts.
+ */
+ for (i = 0; i != OHCI_NO_INTRS; i++) {
+ sc->sc_hcca_p->hcca_interrupt_table[i] =
+ sc->sc_intr_p_last[i | (OHCI_NO_EDS / 2)]->ed_self;
+ }
+ /* flush all cache into memory */
+
+ usb2_bus_mem_flush_all(&sc->sc_bus, &ohci_iterate_hw_softc);
+
+ /* set up the bus struct */
+ sc->sc_bus.methods = &ohci_bus_methods;
+
+ usb2_callout_init_mtx(&sc->sc_tmo_rhsc, &sc->sc_bus.bus_mtx, 0);
+
+#if USB_DEBUG
+ if (ohcidebug > 15) {
+ for (i = 0; i != OHCI_NO_EDS; i++) {
+ printf("ed#%d ", i);
+ ohci_dump_ed(sc->sc_intr_p_last[i]);
+ }
+ printf("iso ");
+ ohci_dump_ed(sc->sc_isoc_p_last);
+ }
+#endif
+
+ sc->sc_bus.usbrev = USB_REV_1_0;
+
+ if (ohci_controller_init(sc)) {
+ return (USB_ERR_INVAL);
+ } else {
+ /* catch any lost interrupts */
+ ohci_do_poll(&sc->sc_bus);
+ return (USB_ERR_NORMAL_COMPLETION);
+ }
+}
+
+/*
+ * shut down the controller when the system is going down
+ */
+void
+ohci_detach(struct ohci_softc *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ usb2_callout_stop(&sc->sc_tmo_rhsc);
+
+ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS);
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* XXX let stray task complete */
+ usb2_pause_mtx(NULL, hz / 20);
+
+ usb2_callout_drain(&sc->sc_tmo_rhsc);
+}
+
+/* NOTE: suspend/resume is called from
+ * interrupt context and cannot sleep!
+ */
+void
+ohci_suspend(ohci_softc_t *sc)
+{
+ uint32_t ctl;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+#if USB_DEBUG
+ DPRINTF("\n");
+ if (ohcidebug > 2) {
+ ohci_dumpregs(sc);
+ }
+#endif
+
+ ctl = OREAD4(sc, OHCI_CONTROL) & ~OHCI_HCFS_MASK;
+ if (sc->sc_control == 0) {
+ /*
+ * Preserve register values, in case that APM BIOS
+ * does not recover them.
+ */
+ sc->sc_control = ctl;
+ sc->sc_intre = OREAD4(sc, OHCI_INTERRUPT_ENABLE);
+ }
+ ctl |= OHCI_HCFS_SUSPEND;
+ OWRITE4(sc, OHCI_CONTROL, ctl);
+
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_RESUME_WAIT));
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+ohci_resume(ohci_softc_t *sc)
+{
+ uint32_t ctl;
+
+#if USB_DEBUG
+ DPRINTF("\n");
+ if (ohcidebug > 2) {
+ ohci_dumpregs(sc);
+ }
+#endif
+ /* some broken BIOSes never initialize the Controller chip */
+ ohci_controller_init(sc);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ if (sc->sc_intre) {
+ OWRITE4(sc, OHCI_INTERRUPT_ENABLE,
+ sc->sc_intre & (OHCI_ALL_INTRS | OHCI_MIE));
+ }
+ if (sc->sc_control)
+ ctl = sc->sc_control;
+ else
+ ctl = OREAD4(sc, OHCI_CONTROL);
+ ctl |= OHCI_HCFS_RESUME;
+ OWRITE4(sc, OHCI_CONTROL, ctl);
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_RESUME_DELAY));
+ ctl = (ctl & ~OHCI_HCFS_MASK) | OHCI_HCFS_OPERATIONAL;
+ OWRITE4(sc, OHCI_CONTROL, ctl);
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_RESUME_RECOVERY));
+ sc->sc_control = sc->sc_intre = 0;
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch any lost interrupts */
+ ohci_do_poll(&sc->sc_bus);
+}
+
+#if USB_DEBUG
+static void
+ohci_dumpregs(ohci_softc_t *sc)
+{
+ struct ohci_hcca *hcca;
+
+ DPRINTF("ohci_dumpregs: rev=0x%08x control=0x%08x command=0x%08x\n",
+ OREAD4(sc, OHCI_REVISION),
+ OREAD4(sc, OHCI_CONTROL),
+ OREAD4(sc, OHCI_COMMAND_STATUS));
+ DPRINTF(" intrstat=0x%08x intre=0x%08x intrd=0x%08x\n",
+ OREAD4(sc, OHCI_INTERRUPT_STATUS),
+ OREAD4(sc, OHCI_INTERRUPT_ENABLE),
+ OREAD4(sc, OHCI_INTERRUPT_DISABLE));
+ DPRINTF(" hcca=0x%08x percur=0x%08x ctrlhd=0x%08x\n",
+ OREAD4(sc, OHCI_HCCA),
+ OREAD4(sc, OHCI_PERIOD_CURRENT_ED),
+ OREAD4(sc, OHCI_CONTROL_HEAD_ED));
+ DPRINTF(" ctrlcur=0x%08x bulkhd=0x%08x bulkcur=0x%08x\n",
+ OREAD4(sc, OHCI_CONTROL_CURRENT_ED),
+ OREAD4(sc, OHCI_BULK_HEAD_ED),
+ OREAD4(sc, OHCI_BULK_CURRENT_ED));
+ DPRINTF(" done=0x%08x fmival=0x%08x fmrem=0x%08x\n",
+ OREAD4(sc, OHCI_DONE_HEAD),
+ OREAD4(sc, OHCI_FM_INTERVAL),
+ OREAD4(sc, OHCI_FM_REMAINING));
+ DPRINTF(" fmnum=0x%08x perst=0x%08x lsthrs=0x%08x\n",
+ OREAD4(sc, OHCI_FM_NUMBER),
+ OREAD4(sc, OHCI_PERIODIC_START),
+ OREAD4(sc, OHCI_LS_THRESHOLD));
+ DPRINTF(" desca=0x%08x descb=0x%08x stat=0x%08x\n",
+ OREAD4(sc, OHCI_RH_DESCRIPTOR_A),
+ OREAD4(sc, OHCI_RH_DESCRIPTOR_B),
+ OREAD4(sc, OHCI_RH_STATUS));
+ DPRINTF(" port1=0x%08x port2=0x%08x\n",
+ OREAD4(sc, OHCI_RH_PORT_STATUS(1)),
+ OREAD4(sc, OHCI_RH_PORT_STATUS(2)));
+
+ hcca = ohci_get_hcca(sc);
+
+ DPRINTF(" HCCA: frame_number=0x%04x done_head=0x%08x\n",
+ le32toh(hcca->hcca_frame_number),
+ le32toh(hcca->hcca_done_head));
+}
+static void
+ohci_dump_tds(ohci_td_t *std)
+{
+ for (; std; std = std->obj_next) {
+ if (ohci_dump_td(std)) {
+ break;
+ }
+ }
+}
+
+static uint8_t
+ohci_dump_td(ohci_td_t *std)
+{
+ uint32_t td_flags;
+ uint8_t temp;
+
+ usb2_pc_cpu_invalidate(std->page_cache);
+
+ td_flags = le32toh(std->td_flags);
+ temp = (std->td_next == 0);
+
+ printf("TD(%p) at 0x%08x: %s%s%s%s%s delay=%d ec=%d "
+ "cc=%d\ncbp=0x%08x next=0x%08x be=0x%08x\n",
+ std, le32toh(std->td_self),
+ (td_flags & OHCI_TD_R) ? "-R" : "",
+ (td_flags & OHCI_TD_OUT) ? "-OUT" : "",
+ (td_flags & OHCI_TD_IN) ? "-IN" : "",
+ ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_1) ? "-TOG1" : "",
+ ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_0) ? "-TOG0" : "",
+ OHCI_TD_GET_DI(td_flags),
+ OHCI_TD_GET_EC(td_flags),
+ OHCI_TD_GET_CC(td_flags),
+ le32toh(std->td_cbp),
+ le32toh(std->td_next),
+ le32toh(std->td_be));
+
+ return (temp);
+}
+
+static uint8_t
+ohci_dump_itd(ohci_itd_t *sitd)
+{
+ uint32_t itd_flags;
+ uint16_t i;
+ uint8_t temp;
+
+ usb2_pc_cpu_invalidate(sitd->page_cache);
+
+ itd_flags = le32toh(sitd->itd_flags);
+ temp = (sitd->itd_next == 0);
+
+ printf("ITD(%p) at 0x%08x: sf=%d di=%d fc=%d cc=%d\n"
+ "bp0=0x%08x next=0x%08x be=0x%08x\n",
+ sitd, le32toh(sitd->itd_self),
+ OHCI_ITD_GET_SF(itd_flags),
+ OHCI_ITD_GET_DI(itd_flags),
+ OHCI_ITD_GET_FC(itd_flags),
+ OHCI_ITD_GET_CC(itd_flags),
+ le32toh(sitd->itd_bp0),
+ le32toh(sitd->itd_next),
+ le32toh(sitd->itd_be));
+ for (i = 0; i < OHCI_ITD_NOFFSET; i++) {
+ printf("offs[%d]=0x%04x ", i,
+ (uint32_t)le16toh(sitd->itd_offset[i]));
+ }
+ printf("\n");
+
+ return (temp);
+}
+
+static void
+ohci_dump_itds(ohci_itd_t *sitd)
+{
+ for (; sitd; sitd = sitd->obj_next) {
+ if (ohci_dump_itd(sitd)) {
+ break;
+ }
+ }
+}
+
+static void
+ohci_dump_ed(ohci_ed_t *sed)
+{
+ uint32_t ed_flags;
+ uint32_t ed_headp;
+
+ usb2_pc_cpu_invalidate(sed->page_cache);
+
+ ed_flags = le32toh(sed->ed_flags);
+ ed_headp = le32toh(sed->ed_headp);
+
+ printf("ED(%p) at 0x%08x: addr=%d endpt=%d maxp=%d flags=%s%s%s%s%s\n"
+ "tailp=0x%08x headflags=%s%s headp=0x%08x nexted=0x%08x\n",
+ sed, le32toh(sed->ed_self),
+ OHCI_ED_GET_FA(ed_flags),
+ OHCI_ED_GET_EN(ed_flags),
+ OHCI_ED_GET_MAXP(ed_flags),
+ (ed_flags & OHCI_ED_DIR_OUT) ? "-OUT" : "",
+ (ed_flags & OHCI_ED_DIR_IN) ? "-IN" : "",
+ (ed_flags & OHCI_ED_SPEED) ? "-LOWSPEED" : "",
+ (ed_flags & OHCI_ED_SKIP) ? "-SKIP" : "",
+ (ed_flags & OHCI_ED_FORMAT_ISO) ? "-ISO" : "",
+ le32toh(sed->ed_tailp),
+ (ed_headp & OHCI_HALTED) ? "-HALTED" : "",
+ (ed_headp & OHCI_TOGGLECARRY) ? "-CARRY" : "",
+ le32toh(sed->ed_headp),
+ le32toh(sed->ed_next));
+}
+
+#endif
+
+static void
+ohci_transfer_intr_enqueue(struct usb2_xfer *xfer)
+{
+ /* check for early completion */
+ if (ohci_check_transfer(xfer)) {
+ return;
+ }
+ /* put transfer on interrupt queue */
+ usb2_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usb2_transfer_timeout_ms(xfer, &ohci_timeout, xfer->timeout);
+ }
+}
+
+#define OHCI_APPEND_QH(sed,last) (last) = _ohci_append_qh(sed,last)
+static ohci_ed_t *
+_ohci_append_qh(ohci_ed_t *sed, ohci_ed_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", sed, last);
+
+ if (sed->prev != NULL) {
+ /* should not happen */
+ DPRINTFN(0, "ED already linked!\n");
+ return (last);
+ }
+ /* (sc->sc_bus.bus_mtx) must be locked */
+
+ sed->next = last->next;
+ sed->ed_next = last->ed_next;
+ sed->ed_tailp = 0;
+
+ sed->prev = last;
+
+ usb2_pc_cpu_flush(sed->page_cache);
+
+ /*
+ * the last->next->prev is never followed: sed->next->prev = sed;
+ */
+
+ last->next = sed;
+ last->ed_next = sed->ed_self;
+
+ usb2_pc_cpu_flush(last->page_cache);
+
+ return (sed);
+}
+
+#define OHCI_REMOVE_QH(sed,last) (last) = _ohci_remove_qh(sed,last)
+static ohci_ed_t *
+_ohci_remove_qh(ohci_ed_t *sed, ohci_ed_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", sed, last);
+
+ /* (sc->sc_bus.bus_mtx) must be locked */
+
+ /* only remove if not removed from a queue */
+ if (sed->prev) {
+
+ sed->prev->next = sed->next;
+ sed->prev->ed_next = sed->ed_next;
+
+ usb2_pc_cpu_flush(sed->prev->page_cache);
+
+ if (sed->next) {
+ sed->next->prev = sed->prev;
+ usb2_pc_cpu_flush(sed->next->page_cache);
+ }
+ last = ((last == sed) ? sed->prev : last);
+
+ sed->prev = 0;
+
+ usb2_pc_cpu_flush(sed->page_cache);
+ }
+ return (last);
+}
+
+static void
+ohci_isoc_done(struct usb2_xfer *xfer)
+{
+ uint8_t nframes;
+ uint32_t *plen = xfer->frlengths;
+ volatile uint16_t *olen;
+ uint16_t len = 0;
+ ohci_itd_t *td = xfer->td_transfer_first;
+
+ while (1) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+#if USB_DEBUG
+ if (ohcidebug > 5) {
+ DPRINTF("isoc TD\n");
+ ohci_dump_itd(td);
+ }
+#endif
+ usb2_pc_cpu_invalidate(td->page_cache);
+
+ nframes = td->frames;
+ olen = &td->itd_offset[0];
+
+ if (nframes > 8) {
+ nframes = 8;
+ }
+ while (nframes--) {
+ len = le16toh(*olen);
+
+ if ((len >> 12) == OHCI_CC_NOT_ACCESSED) {
+ len = 0;
+ } else {
+ len &= ((1 << 12) - 1);
+ }
+
+ if (len > *plen) {
+ len = 0;/* invalid length */
+ }
+ *plen = len;
+ plen++;
+ olen++;
+ }
+
+ if (((void *)td) == xfer->td_transfer_last) {
+ break;
+ }
+ td = td->obj_next;
+ }
+
+ xfer->aframes = xfer->nframes;
+ ohci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
+}
+
+#if USB_DEBUG
+static const char *const
+ ohci_cc_strs[] =
+{
+ "NO_ERROR",
+ "CRC",
+ "BIT_STUFFING",
+ "DATA_TOGGLE_MISMATCH",
+
+ "STALL",
+ "DEVICE_NOT_RESPONDING",
+ "PID_CHECK_FAILURE",
+ "UNEXPECTED_PID",
+
+ "DATA_OVERRUN",
+ "DATA_UNDERRUN",
+ "BUFFER_OVERRUN",
+ "BUFFER_UNDERRUN",
+
+ "reserved",
+ "reserved",
+ "NOT_ACCESSED",
+ "NOT_ACCESSED"
+};
+
+#endif
+
+static usb2_error_t
+ohci_non_isoc_done_sub(struct usb2_xfer *xfer)
+{
+ ohci_td_t *td;
+ ohci_td_t *td_alt_next;
+ uint32_t temp;
+ uint32_t phy_start;
+ uint32_t phy_end;
+ uint32_t td_flags;
+ uint16_t cc;
+
+ td = xfer->td_transfer_cache;
+ td_alt_next = td->alt_next;
+ td_flags = 0;
+
+ if (xfer->aframes != xfer->nframes) {
+ xfer->frlengths[xfer->aframes] = 0;
+ }
+ while (1) {
+
+ usb2_pc_cpu_invalidate(td->page_cache);
+ phy_start = le32toh(td->td_cbp);
+ td_flags = le32toh(td->td_flags);
+ cc = OHCI_TD_GET_CC(td_flags);
+
+ if (phy_start) {
+ /*
+ * short transfer - compute the number of remaining
+ * bytes in the hardware buffer:
+ */
+ phy_end = le32toh(td->td_be);
+ temp = (OHCI_PAGE(phy_start ^ phy_end) ?
+ (OHCI_PAGE_SIZE + 1) : 0x0001);
+ temp += OHCI_PAGE_OFFSET(phy_end);
+ temp -= OHCI_PAGE_OFFSET(phy_start);
+
+ if (temp > td->len) {
+ /* guard against corruption */
+ cc = OHCI_CC_STALL;
+ } else if (xfer->aframes != xfer->nframes) {
+ /*
+ * Sum up total transfer length
+ * in "frlengths[]":
+ */
+ xfer->frlengths[xfer->aframes] += td->len - temp;
+ }
+ } else {
+ if (xfer->aframes != xfer->nframes) {
+ /* transfer was complete */
+ xfer->frlengths[xfer->aframes] += td->len;
+ }
+ }
+ /* Check for last transfer */
+ if (((void *)td) == xfer->td_transfer_last) {
+ td = NULL;
+ break;
+ }
+ /* Check transfer status */
+ if (cc) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (phy_start) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ td = td->alt_next;
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ break;
+ }
+ td = td->obj_next;
+
+ if (td->alt_next != td_alt_next) {
+ /* this USB frame is complete */
+ break;
+ }
+ }
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ DPRINTFN(16, "error cc=%d (%s)\n",
+ cc, ohci_cc_strs[cc]);
+
+ return ((cc == 0) ? USB_ERR_NORMAL_COMPLETION :
+ (cc == OHCI_CC_STALL) ? USB_ERR_STALLED : USB_ERR_IOERROR);
+}
+
+static void
+ohci_non_isoc_done(struct usb2_xfer *xfer)
+{
+ usb2_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+#if USB_DEBUG
+ if (ohcidebug > 10) {
+ ohci_dump_tds(xfer->td_transfer_first);
+ }
+#endif
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+
+ if (xfer->flags_int.control_hdr) {
+
+ err = ohci_non_isoc_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+
+ err = ohci_non_isoc_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 = ohci_non_isoc_done_sub(xfer);
+ }
+done:
+ ohci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * ohci_check_transfer_sub
+ *------------------------------------------------------------------------*/
+static void
+ohci_check_transfer_sub(struct usb2_xfer *xfer)
+{
+ ohci_td_t *td;
+ ohci_ed_t *ed;
+ uint32_t phy_start;
+ uint32_t td_flags;
+ uint32_t td_next;
+ uint16_t cc;
+
+ td = xfer->td_transfer_cache;
+
+ while (1) {
+
+ usb2_pc_cpu_invalidate(td->page_cache);
+ phy_start = le32toh(td->td_cbp);
+ td_flags = le32toh(td->td_flags);
+ td_next = le32toh(td->td_next);
+
+ /* Check for last transfer */
+ if (((void *)td) == xfer->td_transfer_last) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /* Check transfer status */
+ cc = OHCI_TD_GET_CC(td_flags);
+ if (cc) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /*
+ * Check if we reached the last packet
+ * or if there is a short packet:
+ */
+
+ if (((td_next & (~0xF)) == OHCI_TD_NEXT_END) || phy_start) {
+ /* follow alt next */
+ td = td->alt_next;
+ break;
+ }
+ td = td->obj_next;
+ }
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ if (td) {
+
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ ed->ed_headp = td->td_self;
+ usb2_pc_cpu_flush(ed->page_cache);
+
+ DPRINTFN(13, "xfer=%p following alt next\n", xfer);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * ohci_check_transfer
+ *
+ * Return values:
+ * 0: USB transfer is not finished
+ * Else: USB transfer is finished
+ *------------------------------------------------------------------------*/
+static uint8_t
+ohci_check_transfer(struct usb2_xfer *xfer)
+{
+ ohci_ed_t *ed;
+ uint32_t ed_flags;
+ uint32_t ed_headp;
+ uint32_t ed_tailp;
+
+ DPRINTFN(13, "xfer=%p checking transfer\n", xfer);
+
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ usb2_pc_cpu_invalidate(ed->page_cache);
+ ed_flags = le32toh(ed->ed_flags);
+ ed_headp = le32toh(ed->ed_headp);
+ ed_tailp = le32toh(ed->ed_tailp);
+
+ if ((ed_flags & OHCI_ED_SKIP) ||
+ (ed_headp & OHCI_HALTED) ||
+ (((ed_headp ^ ed_tailp) & (~0xF)) == 0)) {
+ if (xfer->pipe->methods == &ohci_device_isoc_methods) {
+ /* isochronous transfer */
+ ohci_isoc_done(xfer);
+ } else {
+ if (xfer->flags_int.short_frames_ok) {
+ ohci_check_transfer_sub(xfer);
+ if (xfer->td_transfer_cache) {
+ /* not finished yet */
+ return (0);
+ }
+ }
+ /* store data-toggle */
+ if (ed_headp & OHCI_TOGGLECARRY) {
+ xfer->pipe->toggle_next = 1;
+ } else {
+ xfer->pipe->toggle_next = 0;
+ }
+
+ /* non-isochronous transfer */
+ ohci_non_isoc_done(xfer);
+ }
+ return (1);
+ }
+ DPRINTFN(13, "xfer=%p is still active\n", xfer);
+ return (0);
+}
+
+static void
+ohci_rhsc_enable(ohci_softc_t *sc)
+{
+ DPRINTFN(5, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ sc->sc_eintrs |= OHCI_RHSC;
+ OWRITE4(sc, OHCI_INTERRUPT_ENABLE, OHCI_RHSC);
+
+ /* acknowledge any RHSC interrupt */
+ OWRITE4(sc, OHCI_INTERRUPT_STATUS, OHCI_RHSC);
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &ohci_root_intr_done);
+}
+
+static void
+ohci_interrupt_poll(ohci_softc_t *sc)
+{
+ struct usb2_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ /*
+ * check if transfer is transferred
+ */
+ if (ohci_check_transfer(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * ohci_interrupt - OHCI interrupt handler
+ *
+ * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler,
+ * hence the interrupt handler will be setup before "sc->sc_bus.bdev"
+ * is present !
+ *------------------------------------------------------------------------*/
+void
+ohci_interrupt(ohci_softc_t *sc)
+{
+ struct ohci_hcca *hcca;
+ uint32_t status;
+ uint32_t done;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ hcca = ohci_get_hcca(sc);
+
+ DPRINTFN(16, "real interrupt\n");
+
+#if USB_DEBUG
+ if (ohcidebug > 15) {
+ ohci_dumpregs(sc);
+ }
+#endif
+
+ done = le32toh(hcca->hcca_done_head);
+
+ /*
+ * The LSb of done is used to inform the HC Driver that an interrupt
+ * condition exists for both the Done list and for another event
+ * recorded in HcInterruptStatus. On an interrupt from the HC, the
+ * HC Driver checks the HccaDoneHead Value. If this value is 0, then
+ * the interrupt was caused by other than the HccaDoneHead update
+ * and the HcInterruptStatus register needs to be accessed to
+ * determine that exact interrupt cause. If HccaDoneHead is nonzero,
+ * then a Done list update interrupt is indicated and if the LSb of
+ * done is nonzero, then an additional interrupt event is indicated
+ * and HcInterruptStatus should be checked to determine its cause.
+ */
+ if (done != 0) {
+ status = 0;
+
+ if (done & ~OHCI_DONE_INTRS) {
+ status |= OHCI_WDH;
+ }
+ if (done & OHCI_DONE_INTRS) {
+ status |= OREAD4(sc, OHCI_INTERRUPT_STATUS);
+ }
+ hcca->hcca_done_head = 0;
+
+ usb2_pc_cpu_flush(&sc->sc_hw.hcca_pc);
+ } else {
+ status = OREAD4(sc, OHCI_INTERRUPT_STATUS) & ~OHCI_WDH;
+ }
+
+ status &= ~OHCI_MIE;
+ if (status == 0) {
+ /*
+ * nothing to be done (PCI shared
+ * interrupt)
+ */
+ goto done;
+ }
+ OWRITE4(sc, OHCI_INTERRUPT_STATUS, status); /* Acknowledge */
+
+ status &= sc->sc_eintrs;
+ if (status == 0) {
+ goto done;
+ }
+ if (status & (OHCI_SO | OHCI_RD | OHCI_UE | OHCI_RHSC)) {
+#if 0
+ if (status & OHCI_SO) {
+ /* XXX do what */
+ }
+#endif
+ if (status & OHCI_RD) {
+ printf("%s: resume detect\n", __FUNCTION__);
+ /* XXX process resume detect */
+ }
+ if (status & OHCI_UE) {
+ printf("%s: unrecoverable error, "
+ "controller halted\n", __FUNCTION__);
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+ /* XXX what else */
+ }
+ if (status & OHCI_RHSC) {
+ /*
+ * Disable RHSC interrupt for now, because it will be
+ * on until the port has been reset.
+ */
+ sc->sc_eintrs &= ~OHCI_RHSC;
+ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_RHSC);
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &ohci_root_intr_done);
+
+ /* do not allow RHSC interrupts > 1 per second */
+ usb2_callout_reset(&sc->sc_tmo_rhsc, hz,
+ (void *)&ohci_rhsc_enable, sc);
+ }
+ }
+ status &= ~(OHCI_RHSC | OHCI_WDH | OHCI_SO);
+ if (status != 0) {
+ /* Block unprocessed interrupts. XXX */
+ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, status);
+ sc->sc_eintrs &= ~status;
+ printf("%s: blocking intrs 0x%x\n",
+ __FUNCTION__, status);
+ }
+ /* poll all the USB transfers */
+ ohci_interrupt_poll(sc);
+
+done:
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*
+ * called when a request does not complete
+ */
+static void
+ohci_timeout(void *arg)
+{
+ struct usb2_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ ohci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+ohci_do_poll(struct usb2_bus *bus)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ ohci_interrupt_poll(sc);
+ ohci_root_ctrl_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+ohci_setup_standard_chain_sub(struct ohci_std_temp *temp)
+{
+ struct usb2_page_search buf_res;
+ ohci_td_t *td;
+ ohci_td_t *td_next;
+ ohci_td_t *td_alt_next;
+ uint32_t buf_offset;
+ uint32_t average;
+ uint32_t len_old;
+ uint8_t shortpkt_old;
+ uint8_t precompute;
+
+ td_alt_next = NULL;
+ buf_offset = 0;
+ shortpkt_old = temp->shortpkt;
+ len_old = temp->len;
+ precompute = 1;
+
+ /* software is used to detect short incoming transfers */
+
+ if ((temp->td_flags & htole32(OHCI_TD_DP_MASK)) == htole32(OHCI_TD_IN)) {
+ temp->td_flags |= htole32(OHCI_TD_R);
+ } else {
+ temp->td_flags &= ~htole32(OHCI_TD_R);
+ }
+
+restart:
+
+ td = temp->td;
+ td_next = temp->td_next;
+
+ while (1) {
+
+ if (temp->len == 0) {
+
+ if (temp->shortpkt) {
+ break;
+ }
+ /* send a Zero Length Packet, ZLP, last */
+
+ temp->shortpkt = 1;
+ average = 0;
+
+ } else {
+
+ average = temp->average;
+
+ if (temp->len < average) {
+ if (temp->len % temp->max_frame_size) {
+ temp->shortpkt = 1;
+ }
+ average = temp->len;
+ }
+ }
+
+ if (td_next == NULL) {
+ panic("%s: out of OHCI transfer descriptors!", __FUNCTION__);
+ }
+ /* get next TD */
+
+ td = td_next;
+ td_next = td->obj_next;
+
+ /* check if we are pre-computing */
+
+ if (precompute) {
+
+ /* update remaining length */
+
+ temp->len -= average;
+
+ continue;
+ }
+ /* fill out current TD */
+ td->td_flags = temp->td_flags;
+
+ /* the next TD uses TOGGLE_CARRY */
+ temp->td_flags &= ~htole32(OHCI_TD_TOGGLE_MASK);
+
+ if (average == 0) {
+
+ td->td_cbp = 0;
+ td->td_be = ~0;
+ td->len = 0;
+
+ } else {
+
+ usb2_get_page(temp->pc, buf_offset, &buf_res);
+ td->td_cbp = htole32(buf_res.physaddr);
+ buf_offset += (average - 1);
+
+ usb2_get_page(temp->pc, buf_offset, &buf_res);
+ td->td_be = htole32(buf_res.physaddr);
+ buf_offset++;
+
+ td->len = average;
+
+ /* update remaining length */
+
+ temp->len -= average;
+ }
+
+ if ((td_next == td_alt_next) && temp->setup_alt_next) {
+ /* we need to receive these frames one by one ! */
+ td->td_flags &= htole32(~OHCI_TD_INTR_MASK);
+ td->td_flags |= htole32(OHCI_TD_SET_DI(1));
+ td->td_next = htole32(OHCI_TD_NEXT_END);
+ } else {
+ if (td_next) {
+ /* link the current TD with the next one */
+ td->td_next = td_next->td_self;
+ }
+ }
+
+ td->alt_next = td_alt_next;
+
+ usb2_pc_cpu_flush(td->page_cache);
+ }
+
+ if (precompute) {
+ precompute = 0;
+
+ /* setup alt next pointer, if any */
+ if (temp->short_frames_ok) {
+ if (temp->setup_alt_next) {
+ td_alt_next = td_next;
+ }
+ } else {
+ /* we use this field internally */
+ td_alt_next = td_next;
+ }
+
+ /* restore */
+ temp->shortpkt = shortpkt_old;
+ temp->len = len_old;
+ goto restart;
+ }
+ temp->td = td;
+ temp->td_next = td_next;
+}
+
+static void
+ohci_setup_standard_chain(struct usb2_xfer *xfer, ohci_ed_t **ed_last)
+{
+ struct ohci_std_temp temp;
+ struct usb2_pipe_methods *methods;
+ ohci_ed_t *ed;
+ ohci_td_t *td;
+ uint32_t ed_flags;
+ uint32_t x;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpoint),
+ xfer->sumlen, usb2_get_speed(xfer->xroot->udev));
+
+ temp.average = xfer->max_usb2_frame_size;
+ temp.max_frame_size = xfer->max_frame_size;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ temp.td = NULL;
+ temp.td_next = td;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.short_frames_ok = xfer->flags_int.short_frames_ok;
+
+ methods = xfer->pipe->methods;
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+
+ temp.td_flags = htole32(OHCI_TD_SETUP | OHCI_TD_NOCC |
+ OHCI_TD_TOGGLE_0 | OHCI_TD_NOINTR);
+
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.shortpkt = temp.len ? 1 : 0;
+
+ ohci_setup_standard_chain_sub(&temp);
+
+ /*
+ * XXX assume that the setup message is
+ * contained within one USB packet:
+ */
+ xfer->pipe->toggle_next = 1;
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+ temp.td_flags = htole32(OHCI_TD_NOCC | OHCI_TD_NOINTR);
+
+ /* set data toggle */
+
+ if (xfer->pipe->toggle_next) {
+ temp.td_flags |= htole32(OHCI_TD_TOGGLE_1);
+ } else {
+ temp.td_flags |= htole32(OHCI_TD_TOGGLE_0);
+ }
+
+ /* set endpoint direction */
+
+ if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) {
+ temp.td_flags |= htole32(OHCI_TD_IN);
+ } else {
+ temp.td_flags |= htole32(OHCI_TD_OUT);
+ }
+
+ while (x != xfer->nframes) {
+
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+ temp.pc = xfer->frbuffers + x;
+
+ x++;
+
+ if (x == xfer->nframes) {
+ temp.setup_alt_next = 0;
+ }
+ if (temp.len == 0) {
+
+ /* make sure that we send an USB packet */
+
+ temp.shortpkt = 0;
+
+ } else {
+
+ /* regular data transfer */
+
+ temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ ohci_setup_standard_chain_sub(&temp);
+ }
+
+ /* check if we should append a status stage */
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ /*
+ * Send a DATA1 message and invert the current endpoint
+ * direction.
+ */
+
+ /* set endpoint direction and data toggle */
+
+ if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) {
+ temp.td_flags = htole32(OHCI_TD_OUT |
+ OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1));
+ } else {
+ temp.td_flags = htole32(OHCI_TD_IN |
+ OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1));
+ }
+
+ temp.len = 0;
+ temp.pc = NULL;
+ temp.shortpkt = 0;
+
+ ohci_setup_standard_chain_sub(&temp);
+ }
+ td = temp.td;
+
+ td->td_next = htole32(OHCI_TD_NEXT_END);
+ td->td_flags &= ~htole32(OHCI_TD_INTR_MASK);
+ td->td_flags |= htole32(OHCI_TD_SET_DI(1));
+
+ usb2_pc_cpu_flush(td->page_cache);
+
+ /* must have at least one frame! */
+
+ xfer->td_transfer_last = td;
+
+#if USB_DEBUG
+ if (ohcidebug > 8) {
+ DPRINTF("nexttog=%d; data before transfer:\n",
+ xfer->pipe->toggle_next);
+ ohci_dump_tds(xfer->td_transfer_first);
+ }
+#endif
+
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ ed_flags = (OHCI_ED_SET_FA(xfer->address) |
+ OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpoint)) |
+ OHCI_ED_SET_MAXP(xfer->max_frame_size));
+
+ ed_flags |= (OHCI_ED_FORMAT_GEN | OHCI_ED_DIR_TD);
+
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ ed_flags |= OHCI_ED_SPEED;
+ }
+ ed->ed_flags = htole32(ed_flags);
+
+ td = xfer->td_transfer_first;
+
+ ed->ed_headp = td->td_self;
+
+ if (xfer->xroot->udev->pwr_save.suspended == 0) {
+ /* the append function will flush the endpoint descriptor */
+ OHCI_APPEND_QH(ed, *ed_last);
+
+ if (methods == &ohci_device_bulk_methods) {
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF);
+ }
+ if (methods == &ohci_device_ctrl_methods) {
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF);
+ }
+ } else {
+ usb2_pc_cpu_flush(ed->page_cache);
+ }
+}
+
+static void
+ohci_root_intr_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+ uint32_t hstatus;
+ uint16_t i;
+ uint16_t m;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_PRE_DATA) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ ohci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* setup buffer */
+ std->ptr = sc->sc_hub_idata;
+ std->len = sizeof(sc->sc_hub_idata);
+
+ /* clear any old interrupt data */
+ bzero(sc->sc_hub_idata, sizeof(sc->sc_hub_idata));
+
+ hstatus = OREAD4(sc, OHCI_RH_STATUS);
+ DPRINTF("sc=%p xfer=%p hstatus=0x%08x\n",
+ sc, xfer, hstatus);
+
+ /* set bits */
+ m = (sc->sc_noport + 1);
+ if (m > (8 * sizeof(sc->sc_hub_idata))) {
+ m = (8 * sizeof(sc->sc_hub_idata));
+ }
+ for (i = 1; i < m; i++) {
+ /* pick out CHANGE bits from the status register */
+ if (OREAD4(sc, OHCI_RH_PORT_STATUS(i)) >> 16) {
+ sc->sc_hub_idata[i / 8] |= 1 << (i % 8);
+ DPRINTF("port %d changed\n", i);
+ }
+ }
+done:
+ return;
+}
+
+/* NOTE: "done" can be run two times in a row,
+ * from close and from interrupt
+ */
+static void
+ohci_device_done(struct usb2_xfer *xfer, usb2_error_t error)
+{
+ struct usb2_pipe_methods *methods = xfer->pipe->methods;
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+ ohci_ed_t *ed;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+
+ DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n",
+ xfer, xfer->pipe, error);
+
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+ if (ed) {
+ usb2_pc_cpu_invalidate(ed->page_cache);
+ }
+ if (methods == &ohci_device_bulk_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_bulk_p_last);
+ }
+ if (methods == &ohci_device_ctrl_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_ctrl_p_last);
+ }
+ if (methods == &ohci_device_intr_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ if (methods == &ohci_device_isoc_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_isoc_p_last);
+ }
+ xfer->td_transfer_first = NULL;
+ xfer->td_transfer_last = NULL;
+
+ /* dequeue transfer and start next transfer */
+ usb2_transfer_done(xfer, error);
+}
+
+/*------------------------------------------------------------------------*
+ * ohci bulk support
+ *------------------------------------------------------------------------*/
+static void
+ohci_device_bulk_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_bulk_close(struct usb2_xfer *xfer)
+{
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ohci_device_bulk_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_bulk_start(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ohci_setup_standard_chain(xfer, &sc->sc_bulk_p_last);
+
+ /* put transfer on interrupt queue */
+ ohci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ohci_device_bulk_methods =
+{
+ .open = ohci_device_bulk_open,
+ .close = ohci_device_bulk_close,
+ .enter = ohci_device_bulk_enter,
+ .start = ohci_device_bulk_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ohci control support
+ *------------------------------------------------------------------------*/
+static void
+ohci_device_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_ctrl_close(struct usb2_xfer *xfer)
+{
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ohci_device_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_ctrl_start(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ohci_setup_standard_chain(xfer, &sc->sc_ctrl_p_last);
+
+ /* put transfer on interrupt queue */
+ ohci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ohci_device_ctrl_methods =
+{
+ .open = ohci_device_ctrl_open,
+ .close = ohci_device_ctrl_close,
+ .enter = ohci_device_ctrl_enter,
+ .start = ohci_device_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ohci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+ohci_device_intr_open(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+ uint16_t best;
+ uint16_t bit;
+ uint16_t x;
+
+ best = 0;
+ bit = OHCI_NO_EDS / 2;
+ while (bit) {
+ if (xfer->interval >= bit) {
+ x = bit;
+ best = bit;
+ while (x & bit) {
+ if (sc->sc_intr_stat[x] <
+ sc->sc_intr_stat[best]) {
+ best = x;
+ }
+ x++;
+ }
+ break;
+ }
+ bit >>= 1;
+ }
+
+ sc->sc_intr_stat[best]++;
+ xfer->qh_pos = best;
+
+ DPRINTFN(3, "best=%d interval=%d\n",
+ best, xfer->interval);
+}
+
+static void
+ohci_device_intr_close(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_intr_stat[xfer->qh_pos]--;
+
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ohci_device_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_intr_start(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ohci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]);
+
+ /* put transfer on interrupt queue */
+ ohci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ohci_device_intr_methods =
+{
+ .open = ohci_device_intr_open,
+ .close = ohci_device_intr_close,
+ .enter = ohci_device_intr_enter,
+ .start = ohci_device_intr_start,
+ .enter_is_cancelable = 1,