ce0da37e7d
in SUPER-speed mode, USB 3.0. This feature has not been tested yet, due to lack of hardware. This feature is useful when implementing protocols like UASP, USB attached SCSI which promises higher USB mass storage throughput. This patch also implements support for hardware processing of endpoints for increased performance. The switching to hardware processing of an endpoint is done via a callback to the USB controller driver. The stream feature is implemented like a variant of a hardware USB protocol. USB controller drivers implementing device mode needs to be updated to implement the new "xfer_stall" USB controller method and remove the "xfer" argument from the "set_stall" method. The API's toward existing USB drivers are preserved. To setup a USB transfer in stream mode, set the "stream_id" field of the USB config structure to the desired value. The maximum number of BULK streams is currently hardcoded and limited to 8 via a define in usb_freebsd.h. All USB drivers should be re-compiled after this change. LibUSB will be updated next week to support streams mode. A new IOCTL to setup BULK streams as already been implemented. The ugen device nodes currently only supports stream ID zero. The FreeBSD version has been bumped. MFC after: 2 weeks
2813 lines
63 KiB
C
2813 lines
63 KiB
C
/* $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 <sys/stdint.h>
|
|
#include <sys/stddef.h>
|
|
#include <sys/param.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/types.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/module.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/condvar.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/sx.h>
|
|
#include <sys/unistd.h>
|
|
#include <sys/callout.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/priv.h>
|
|
|
|
#include <dev/usb/usb.h>
|
|
#include <dev/usb/usbdi.h>
|
|
|
|
#define USB_DEBUG_VAR 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_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(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus)
|
|
|
|
#ifdef USB_DEBUG
|
|
static int musbotgdebug = 0;
|
|
|
|
static SYSCTL_NODE(_hw_usb, OID_AUTO, musbotg, CTLFLAG_RW, 0, "USB musbotg");
|
|
SYSCTL_INT(_hw_usb_musbotg, OID_AUTO, debug, CTLFLAG_RW,
|
|
&musbotgdebug, 0, "Debug level");
|
|
#endif
|
|
|
|
/* prototypes */
|
|
|
|
struct usb_bus_methods musbotg_bus_methods;
|
|
struct usb_pipe_methods musbotg_device_bulk_methods;
|
|
struct usb_pipe_methods musbotg_device_ctrl_methods;
|
|
struct usb_pipe_methods musbotg_device_intr_methods;
|
|
struct usb_pipe_methods musbotg_device_isoc_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 usb_xfer *, usb_error_t);
|
|
static void musbotg_do_poll(struct usb_bus *);
|
|
static void musbotg_standard_done(struct usb_xfer *);
|
|
static void musbotg_interrupt_poll(struct musbotg_softc *);
|
|
static void musbotg_root_intr(struct musbotg_softc *);
|
|
|
|
/*
|
|
* Here is a configuration that the chip supports.
|
|
*/
|
|
static const struct usb_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 usb_device *udev,
|
|
const struct usb_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 musbotg_softc *sc)
|
|
{
|
|
uint8_t temp;
|
|
|
|
if (!(sc->sc_flags.status_suspend)) {
|
|
return;
|
|
}
|
|
|
|
temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
|
|
temp |= MUSB2_MASK_RESUME;
|
|
MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
|
|
|
|
/* wait 8 milliseconds */
|
|
/* Wait for reset to complete. */
|
|
usb_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 usb_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) {
|
|
/* do not stall at this point */
|
|
td->did_stall = 1;
|
|
/* 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) {
|
|
goto not_complete;
|
|
}
|
|
if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) {
|
|
goto not_complete;
|
|
}
|
|
/* clear did stall flag */
|
|
td->did_stall = 0;
|
|
/* 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);
|
|
MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
|
|
MUSB2_MASK_CSR0L_RXPKTRDY_CLR);
|
|
goto not_complete;
|
|
}
|
|
if (count != sizeof(req)) {
|
|
DPRINTFN(0, "Unsupported SETUP packet "
|
|
"length, %d bytes\n", count);
|
|
MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
|
|
MUSB2_MASK_CSR0L_RXPKTRDY_CLR);
|
|
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 */
|
|
usbd_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:
|
|
/* 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;
|
|
}
|
|
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 usb_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;
|
|
|
|
usbd_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);
|
|
}
|
|
usbd_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 usb_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;
|
|
|
|
usbd_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) {
|
|
|
|
usbd_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 usb_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;
|
|
|
|
usbd_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);
|
|
}
|
|
usbd_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 usb_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;
|
|
|
|
usbd_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) {
|
|
|
|
usbd_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 usb_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 usb_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 */
|
|
musbotg_root_intr(sc);
|
|
}
|
|
} else {
|
|
if (sc->sc_flags.status_vbus) {
|
|
sc->sc_flags.status_vbus = 0;
|
|
sc->sc_flags.status_bus_reset = 0;
|
|
sc->sc_flags.status_suspend = 0;
|
|
sc->sc_flags.change_suspend = 0;
|
|
sc->sc_flags.change_connect = 1;
|
|
|
|
/* complete root HUB interrupt endpoint */
|
|
musbotg_root_intr(sc);
|
|
}
|
|
}
|
|
|
|
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 */
|
|
musbotg_root_intr(sc);
|
|
}
|
|
/* 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 = temp->did_stall;
|
|
td->short_pkt = temp->short_pkt;
|
|
td->alt_next = temp->setup_alt_next;
|
|
}
|
|
|
|
static void
|
|
musbotg_setup_standard_chain(struct usb_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->endpointno),
|
|
xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
|
|
|
|
temp.max_frame_size = xfer->max_frame_size;
|
|
|
|
td = xfer->td_start[0];
|
|
xfer->td_transfer_first = td;
|
|
xfer->td_transfer_cache = td;
|
|
|
|
/* setup temp */
|
|
|
|
temp.pc = NULL;
|
|
temp.td = NULL;
|
|
temp.td_next = xfer->td_start[0];
|
|
temp.offset = 0;
|
|
temp.setup_alt_next = xfer->flags_int.short_frames_ok;
|
|
temp.did_stall = !xfer->flags_int.control_stall;
|
|
|
|
sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
|
|
ep_no = (xfer->endpointno & UE_ADDR);
|
|
|
|
/* check if we should prepend a setup message */
|
|
|
|
if (xfer->flags_int.control_xfr) {
|
|
if (xfer->flags_int.control_hdr) {
|
|
|
|
temp.func = &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->endpointno & 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) {
|
|
if (xfer->flags_int.control_xfr) {
|
|
if (xfer->flags_int.control_act) {
|
|
temp.setup_alt_next = 0;
|
|
}
|
|
} else {
|
|
temp.setup_alt_next = 0;
|
|
}
|
|
}
|
|
if (temp.len == 0) {
|
|
|
|
/* make sure that we send an USB packet */
|
|
|
|
temp.short_pkt = 0;
|
|
|
|
} else {
|
|
|
|
/* regular data transfer */
|
|
|
|
temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* check for control transfer */
|
|
if (xfer->flags_int.control_xfr) {
|
|
|
|
/* always setup a valid "pc" pointer for status and sync */
|
|
temp.pc = xfer->frbuffers + 0;
|
|
temp.len = 0;
|
|
temp.short_pkt = 0;
|
|
temp.setup_alt_next = 0;
|
|
|
|
/* check if we should append a status stage */
|
|
if (!xfer->flags_int.control_act) {
|
|
/*
|
|
* Send a DATA1 message and invert the current
|
|
* endpoint direction.
|
|
*/
|
|
temp.func = &musbotg_setup_status;
|
|
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 usb_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 usb_xfer *xfer, uint8_t on)
|
|
{
|
|
struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
|
|
uint16_t temp;
|
|
uint8_t ep_no = xfer->endpointno & UE_ADDR;
|
|
|
|
/*
|
|
* Only enable the endpoint interrupt when we are
|
|
* actually waiting for data, hence we are dealing
|
|
* with level triggered interrupts !
|
|
*/
|
|
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 usb_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 */
|
|
usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
|
|
|
|
/* start timeout, if any */
|
|
if (xfer->timeout != 0) {
|
|
usbd_transfer_timeout_ms(xfer,
|
|
&musbotg_timeout, xfer->timeout);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
musbotg_root_intr(struct musbotg_softc *sc)
|
|
{
|
|
DPRINTFN(8, "\n");
|
|
|
|
USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
|
|
|
|
/* set port bit */
|
|
sc->sc_hub_idata[0] = 0x02; /* we only have one port */
|
|
|
|
uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
|
|
sizeof(sc->sc_hub_idata));
|
|
}
|
|
|
|
static usb_error_t
|
|
musbotg_standard_done_sub(struct usb_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 usb_xfer *xfer)
|
|
{
|
|
usb_error_t err = 0;
|
|
|
|
DPRINTFN(12, "xfer=%p endpoint=%p transfer done\n",
|
|
xfer, xfer->endpoint);
|
|
|
|
/* reset scanner */
|
|
|
|
xfer->td_transfer_cache = xfer->td_transfer_first;
|
|
|
|
if (xfer->flags_int.control_xfr) {
|
|
|
|
if (xfer->flags_int.control_hdr) {
|
|
|
|
err = 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 usb_xfer *xfer, usb_error_t error)
|
|
{
|
|
USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
|
|
|
|
DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n",
|
|
xfer, xfer->endpoint, error);
|
|
|
|
if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
|
|
|
|
musbotg_ep_int_set(xfer, 0);
|
|
|
|
DPRINTFN(14, "disabled interrupts on endpoint\n");
|
|
}
|
|
/* dequeue transfer and start next transfer */
|
|
usbd_transfer_done(xfer, error);
|
|
}
|
|
|
|
static void
|
|
musbotg_xfer_stall(struct usb_xfer *xfer)
|
|
{
|
|
musbotg_device_done(xfer, USB_ERR_STALLED);
|
|
}
|
|
|
|
static void
|
|
musbotg_set_stall(struct usb_device *udev,
|
|
struct usb_endpoint *ep, uint8_t *did_stall)
|
|
{
|
|
struct musbotg_softc *sc;
|
|
uint8_t ep_no;
|
|
|
|
USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
|
|
|
|
DPRINTFN(4, "endpoint=%p\n", ep);
|
|
|
|
/* set FORCESTALL */
|
|
sc = MUSBOTG_BUS2SC(udev->bus);
|
|
|
|
ep_no = (ep->edesc->bEndpointAddress & UE_ADDR);
|
|
|
|
/* select endpoint */
|
|
MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no);
|
|
|
|
if (ep->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_2(sc, MUSB2_REG_TXMAXP, wMaxPacket);
|
|
MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH,
|
|
MUSB2_MASK_CSRH_TXMODE | temp);
|
|
break;
|
|
case UE_ISOCHRONOUS:
|
|
MUSB2_WRITE_2(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_2(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_2(sc, MUSB2_REG_RXMAXP, wMaxPacket);
|
|
MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH,
|
|
MUSB2_MASK_CSRH_RXNYET | temp);
|
|
break;
|
|
case UE_ISOCHRONOUS:
|
|
MUSB2_WRITE_2(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_2(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 usb_device *udev, struct usb_endpoint *ep)
|
|
{
|
|
struct musbotg_softc *sc;
|
|
struct usb_endpoint_descriptor *ed;
|
|
|
|
DPRINTFN(4, "endpoint=%p\n", ep);
|
|
|
|
USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
|
|
|
|
/* check mode */
|
|
if (udev->flags.usb_mode != USB_MODE_DEVICE) {
|
|
/* not supported */
|
|
return;
|
|
}
|
|
/* get softc */
|
|
sc = MUSBOTG_BUS2SC(udev->bus);
|
|
|
|
/* get endpoint descriptor */
|
|
ed = ep->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)));
|
|
}
|
|
|
|
usb_error_t
|
|
musbotg_init(struct musbotg_softc *sc)
|
|
{
|
|
struct usb_hw_ep_profile *pf;
|
|
uint16_t offset;
|
|
uint8_t nrx;
|
|
uint8_t ntx;
|
|
uint8_t temp;
|
|
uint8_t fsize;
|
|
uint8_t frx;
|
|
uint8_t ftx;
|
|
uint8_t dynfifo;
|
|
|
|
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 */
|
|
usb_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) */
|
|
usb_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);
|
|
|
|
dynfifo = (sc->sc_conf_data & MUSB2_MASK_CD_DYNFIFOSZ) ? 1 : 0;
|
|
|
|
if (dynfifo) {
|
|
device_printf(sc->sc_bus.bdev, "Dynamic FIFO sizing detected, "
|
|
"assuming 16Kbytes of FIFO RAM\n");
|
|
}
|
|
|
|
DPRINTFN(2, "HW version: 0x%04x\n",
|
|
MUSB2_READ_1(sc, MUSB2_REG_HWVERS));
|
|
|
|
/* initialise endpoint profiles */
|
|
|
|
offset = 0;
|
|
|
|
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, DYN=%d\n",
|
|
temp, ftx, frx, dynfifo);
|
|
|
|
if (dynfifo) {
|
|
if (frx && (temp <= nrx)) {
|
|
if (temp < 8) {
|
|
frx = 10; /* 1K */
|
|
MUSB2_WRITE_1(sc, MUSB2_REG_RXFIFOSZ,
|
|
MUSB2_VAL_FIFOSZ_512 |
|
|
MUSB2_MASK_FIFODB);
|
|
} else {
|
|
frx = 7; /* 128 bytes */
|
|
MUSB2_WRITE_1(sc, MUSB2_REG_RXFIFOSZ,
|
|
MUSB2_VAL_FIFOSZ_128);
|
|
}
|
|
|
|
MUSB2_WRITE_2(sc, MUSB2_REG_RXFIFOADD,
|
|
offset >> 3);
|
|
|
|
offset += (1 << frx);
|
|
}
|
|
if (ftx && (temp <= ntx)) {
|
|
if (temp < 8) {
|
|
ftx = 10; /* 1K */
|
|
MUSB2_WRITE_1(sc, MUSB2_REG_TXFIFOSZ,
|
|
MUSB2_VAL_FIFOSZ_512 |
|
|
MUSB2_MASK_FIFODB);
|
|
} else {
|
|
ftx = 7; /* 128 bytes */
|
|
MUSB2_WRITE_1(sc, MUSB2_REG_TXFIFOSZ,
|
|
MUSB2_VAL_FIFOSZ_128);
|
|
}
|
|
|
|
MUSB2_WRITE_2(sc, MUSB2_REG_TXFIFOADD,
|
|
offset >> 3);
|
|
|
|
offset += (1 << ftx);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
DPRINTFN(2, "Dynamic FIFO size = %d bytes\n", offset);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
static void
|
|
musbotg_suspend(struct musbotg_softc *sc)
|
|
{
|
|
/* TODO */
|
|
}
|
|
|
|
static void
|
|
musbotg_resume(struct musbotg_softc *sc)
|
|
{
|
|
/* TODO */
|
|
}
|
|
|
|
static void
|
|
musbotg_do_poll(struct usb_bus *bus)
|
|
{
|
|
struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus);
|
|
|
|
USB_BUS_LOCK(&sc->sc_bus);
|
|
musbotg_interrupt_poll(sc);
|
|
USB_BUS_UNLOCK(&sc->sc_bus);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* musbotg bulk support
|
|
*------------------------------------------------------------------------*/
|
|
static void
|
|
musbotg_device_bulk_open(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
musbotg_device_bulk_close(struct usb_xfer *xfer)
|
|
{
|
|
musbotg_device_done(xfer, USB_ERR_CANCELLED);
|
|
}
|
|
|
|
static void
|
|
musbotg_device_bulk_enter(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
musbotg_device_bulk_start(struct usb_xfer *xfer)
|
|
{
|
|
/* setup TDs */
|
|
musbotg_setup_standard_chain(xfer);
|
|
musbotg_start_standard_chain(xfer);
|
|
}
|
|
|
|
struct usb_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,
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* musbotg control support
|
|
*------------------------------------------------------------------------*/
|
|
static void
|
|
musbotg_device_ctrl_open(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
musbotg_device_ctrl_close(struct usb_xfer *xfer)
|
|
{
|
|
musbotg_device_done(xfer, USB_ERR_CANCELLED);
|
|
}
|
|
|
|
static void
|
|
musbotg_device_ctrl_enter(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
musbotg_device_ctrl_start(struct usb_xfer *xfer)
|
|
{
|
|
/* setup TDs */
|
|
musbotg_setup_standard_chain(xfer);
|
|
musbotg_start_standard_chain(xfer);
|
|
}
|
|
|
|
struct usb_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,
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* musbotg interrupt support
|
|
*------------------------------------------------------------------------*/
|
|
static void
|
|
musbotg_device_intr_open(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
musbotg_device_intr_close(struct usb_xfer *xfer)
|
|
{
|
|
musbotg_device_done(xfer, USB_ERR_CANCELLED);
|
|
}
|
|
|
|
static void
|
|
musbotg_device_intr_enter(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
musbotg_device_intr_start(struct usb_xfer *xfer)
|
|
{
|
|
/* setup TDs */
|
|
musbotg_setup_standard_chain(xfer);
|
|
musbotg_start_standard_chain(xfer);
|
|
}
|
|
|
|
struct usb_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,
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* musbotg full speed isochronous support
|
|
*------------------------------------------------------------------------*/
|
|
static void
|
|
musbotg_device_isoc_open(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
musbotg_device_isoc_close(struct usb_xfer *xfer)
|
|
{
|
|
musbotg_device_done(xfer, USB_ERR_CANCELLED);
|
|
}
|
|
|
|
static void
|
|
musbotg_device_isoc_enter(struct usb_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->endpoint->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->endpoint->isoc_next) & MUSB2_MASK_FRAME;
|
|
|
|
if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) {
|
|
fs_frames = (xfer->nframes + 7) / 8;
|
|
} else {
|
|
fs_frames = xfer->nframes;
|
|
}
|
|
|
|
if ((xfer->endpoint->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->endpoint->isoc_next = (nframes + 3) & MUSB2_MASK_FRAME;
|
|
xfer->endpoint->is_synced = 1;
|
|
DPRINTFN(2, "start next=%d\n", xfer->endpoint->isoc_next);
|
|
}
|
|
/*
|
|
* compute how many milliseconds the insertion is ahead of the
|
|
* current frame position:
|
|
*/
|
|
temp = (xfer->endpoint->isoc_next - nframes) & MUSB2_MASK_FRAME;
|
|
|
|
/*
|
|
* pre-compute when the isochronous transfer will be finished:
|
|
*/
|
|
xfer->isoc_time_complete =
|
|
usb_isoc_time_expand(&sc->sc_bus, nframes) + temp +
|
|
fs_frames;
|
|
|
|
/* compute frame number for next insertion */
|
|
xfer->endpoint->isoc_next += fs_frames;
|
|
|
|
/* setup TDs */
|
|
musbotg_setup_standard_chain(xfer);
|
|
}
|
|
|
|
static void
|
|
musbotg_device_isoc_start(struct usb_xfer *xfer)
|
|
{
|
|
/* start TD chain */
|
|
musbotg_start_standard_chain(xfer);
|
|
}
|
|
|
|
struct usb_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,
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* musbotg root control support
|
|
*------------------------------------------------------------------------*
|
|
* Simulate a hardware HUB by handling all the necessary requests.
|
|
*------------------------------------------------------------------------*/
|
|
|
|
static const struct usb_device_descriptor musbotg_devd = {
|
|
.bLength = sizeof(struct usb_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 usb_device_qualifier musbotg_odevd = {
|
|
.bLength = sizeof(struct usb_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 usb_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 usb_interface_descriptor),
|
|
.bDescriptorType = UDESC_INTERFACE,
|
|
.bNumEndpoints = 1,
|
|
.bInterfaceClass = UICLASS_HUB,
|
|
.bInterfaceSubClass = UISUBCLASS_HUB,
|
|
.bInterfaceProtocol = 0,
|
|
},
|
|
.endpd = {
|
|
.bLength = sizeof(struct usb_endpoint_descriptor),
|
|
.bDescriptorType = UDESC_ENDPOINT,
|
|
.bEndpointAddress = (UE_DIR_IN | MUSBOTG_INTR_ENDPT),
|
|
.bmAttributes = UE_INTERRUPT,
|
|
.wMaxPacketSize[0] = 8,
|
|
.bInterval = 255,
|
|
},
|
|
};
|
|
|
|
#define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) }
|
|
|
|
static const struct usb_hub_descriptor_min musbotg_hubd = {
|
|
.bDescLength = sizeof(musbotg_hubd),
|
|
.bDescriptorType = UDESC_HUB,
|
|
.bNbrPorts = 1,
|
|
HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)),
|
|
.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 usb_error_t
|
|
musbotg_roothub_exec(struct usb_device *udev,
|
|
struct usb_device_request *req, const void **pptr, uint16_t *plength)
|
|
{
|
|
struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus);
|
|
const void *ptr;
|
|
uint16_t len;
|
|
uint16_t value;
|
|
uint16_t index;
|
|
usb_error_t err;
|
|
|
|
USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
|
|
|
|
/* buffer reset */
|
|
ptr = (const void *)&sc->sc_hub_temp;
|
|
len = 0;
|
|
err = 0;
|
|
|
|
value = UGETW(req->wValue);
|
|
index = UGETW(req->wIndex);
|
|
|
|
/* demultiplex the control request */
|
|
|
|
switch (req->bmRequestType) {
|
|
case UT_READ_DEVICE:
|
|
switch (req->bRequest) {
|
|
case UR_GET_DESCRIPTOR:
|
|
goto tr_handle_get_descriptor;
|
|
case UR_GET_CONFIG:
|
|
goto tr_handle_get_config;
|
|
case UR_GET_STATUS:
|
|
goto tr_handle_get_status;
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
break;
|
|
|
|
case UT_WRITE_DEVICE:
|
|
switch (req->bRequest) {
|
|
case UR_SET_ADDRESS:
|
|
goto tr_handle_set_address;
|
|
case UR_SET_CONFIG:
|
|
goto tr_handle_set_config;
|
|
case UR_CLEAR_FEATURE:
|
|
goto tr_valid; /* nop */
|
|
case UR_SET_DESCRIPTOR:
|
|
goto tr_valid; /* nop */
|
|
case UR_SET_FEATURE:
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
break;
|
|
|
|
case UT_WRITE_ENDPOINT:
|
|
switch (req->bRequest) {
|
|
case UR_CLEAR_FEATURE:
|
|
switch (UGETW(req->wValue)) {
|
|
case UF_ENDPOINT_HALT:
|
|
goto tr_handle_clear_halt;
|
|
case UF_DEVICE_REMOTE_WAKEUP:
|
|
goto tr_handle_clear_wakeup;
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
break;
|
|
case UR_SET_FEATURE:
|
|
switch (UGETW(req->wValue)) {
|
|
case UF_ENDPOINT_HALT:
|
|
goto tr_handle_set_halt;
|
|
case UF_DEVICE_REMOTE_WAKEUP:
|
|
goto tr_handle_set_wakeup;
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
break;
|
|
case UR_SYNCH_FRAME:
|
|
goto tr_valid; /* nop */
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
break;
|
|
|
|
case UT_READ_ENDPOINT:
|
|
switch (req->bRequest) {
|
|
case UR_GET_STATUS:
|
|
goto tr_handle_get_ep_status;
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
break;
|
|
|
|
case UT_WRITE_INTERFACE:
|
|
switch (req->bRequest) {
|
|
case UR_SET_INTERFACE:
|
|
goto tr_handle_set_interface;
|
|
case UR_CLEAR_FEATURE:
|
|
goto tr_valid; /* nop */
|
|
case UR_SET_FEATURE:
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
break;
|
|
|
|
case UT_READ_INTERFACE:
|
|
switch (req->bRequest) {
|
|
case UR_GET_INTERFACE:
|
|
goto tr_handle_get_interface;
|
|
case UR_GET_STATUS:
|
|
goto tr_handle_get_iface_status;
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
break;
|
|
|
|
case UT_WRITE_CLASS_INTERFACE:
|
|
case UT_WRITE_VENDOR_INTERFACE:
|
|
/* XXX forward */
|
|
break;
|
|
|
|
case UT_READ_CLASS_INTERFACE:
|
|
case UT_READ_VENDOR_INTERFACE:
|
|
/* XXX forward */
|
|
break;
|
|
|
|
case UT_WRITE_CLASS_DEVICE:
|
|
switch (req->bRequest) {
|
|
case UR_CLEAR_FEATURE:
|
|
goto tr_valid;
|
|
case UR_SET_DESCRIPTOR:
|
|
case UR_SET_FEATURE:
|
|
break;
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
break;
|
|
|
|
case UT_WRITE_CLASS_OTHER:
|
|
switch (req->bRequest) {
|
|
case UR_CLEAR_FEATURE:
|
|
goto tr_handle_clear_port_feature;
|
|
case UR_SET_FEATURE:
|
|
goto tr_handle_set_port_feature;
|
|
case UR_CLEAR_TT_BUFFER:
|
|
case UR_RESET_TT:
|
|
case UR_STOP_TT:
|
|
goto tr_valid;
|
|
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
break;
|
|
|
|
case UT_READ_CLASS_OTHER:
|
|
switch (req->bRequest) {
|
|
case UR_GET_TT_STATE:
|
|
goto tr_handle_get_tt_state;
|
|
case UR_GET_STATUS:
|
|
goto tr_handle_get_port_status;
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
break;
|
|
|
|
case UT_READ_CLASS_DEVICE:
|
|
switch (req->bRequest) {
|
|
case UR_GET_DESCRIPTOR:
|
|
goto tr_handle_get_class_descriptor;
|
|
case UR_GET_STATUS:
|
|
goto tr_handle_get_class_status;
|
|
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
break;
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
goto tr_valid;
|
|
|
|
tr_handle_get_descriptor:
|
|
switch (value >> 8) {
|
|
case UDESC_DEVICE:
|
|
if (value & 0xff) {
|
|
goto tr_stalled;
|
|
}
|
|
len = sizeof(musbotg_devd);
|
|
ptr = (const void *)&musbotg_devd;
|
|
goto tr_valid;
|
|
case UDESC_CONFIG:
|
|
if (value & 0xff) {
|
|
goto tr_stalled;
|
|
}
|
|
len = sizeof(musbotg_confd);
|
|
ptr = (const void *)&musbotg_confd;
|
|
goto tr_valid;
|
|
case UDESC_STRING:
|
|
switch (value & 0xff) {
|
|
case 0: /* Language table */
|
|
len = sizeof(musbotg_langtab);
|
|
ptr = (const void *)&musbotg_langtab;
|
|
goto tr_valid;
|
|
|
|
case 1: /* Vendor */
|
|
len = sizeof(musbotg_vendor);
|
|
ptr = (const void *)&musbotg_vendor;
|
|
goto tr_valid;
|
|
|
|
case 2: /* Product */
|
|
len = sizeof(musbotg_product);
|
|
ptr = (const void *)&musbotg_product;
|
|
goto tr_valid;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
goto tr_stalled;
|
|
}
|
|
goto tr_stalled;
|
|
|
|
tr_handle_get_config:
|
|
len = 1;
|
|
sc->sc_hub_temp.wValue[0] = sc->sc_conf;
|
|
goto tr_valid;
|
|
|
|
tr_handle_get_status:
|
|
len = 2;
|
|
USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED);
|
|
goto tr_valid;
|
|
|
|
tr_handle_set_address:
|
|
if (value & 0xFF00) {
|
|
goto tr_stalled;
|
|
}
|
|
sc->sc_rt_addr = value;
|
|
goto tr_valid;
|
|
|
|
tr_handle_set_config:
|
|
if (value >= 2) {
|
|
goto tr_stalled;
|
|
}
|
|
sc->sc_conf = value;
|
|
goto tr_valid;
|
|
|
|
tr_handle_get_interface:
|
|
len = 1;
|
|
sc->sc_hub_temp.wValue[0] = 0;
|
|
goto tr_valid;
|
|
|
|
tr_handle_get_tt_state:
|
|
tr_handle_get_class_status:
|
|
tr_handle_get_iface_status:
|
|
tr_handle_get_ep_status:
|
|
len = 2;
|
|
USETW(sc->sc_hub_temp.wValue, 0);
|
|
goto tr_valid;
|
|
|
|
tr_handle_set_halt:
|
|
tr_handle_set_interface:
|
|
tr_handle_set_wakeup:
|
|
tr_handle_clear_wakeup:
|
|
tr_handle_clear_halt:
|
|
goto tr_valid;
|
|
|
|
tr_handle_clear_port_feature:
|
|
if (index != 1) {
|
|
goto tr_stalled;
|
|
}
|
|
DPRINTFN(8, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
|
|
|
|
switch (value) {
|
|
case UHF_PORT_SUSPEND:
|
|
musbotg_wakeup_peer(sc);
|
|
break;
|
|
|
|
case UHF_PORT_ENABLE:
|
|
sc->sc_flags.port_enabled = 0;
|
|
break;
|
|
|
|
case UHF_PORT_TEST:
|
|
case UHF_PORT_INDICATOR:
|
|
case UHF_C_PORT_ENABLE:
|
|
case UHF_C_PORT_OVER_CURRENT:
|
|
case UHF_C_PORT_RESET:
|
|
/* nops */
|
|
break;
|
|
case UHF_PORT_POWER:
|
|
sc->sc_flags.port_powered = 0;
|
|
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:
|
|
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:
|
|
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);
|
|
len = sizeof(sc->sc_hub_temp.ps);
|
|
goto tr_valid;
|
|
|
|
tr_handle_get_class_descriptor:
|
|
if (value & 0xFF) {
|
|
goto tr_stalled;
|
|
}
|
|
ptr = (const void *)&musbotg_hubd;
|
|
len = sizeof(musbotg_hubd);
|
|
goto tr_valid;
|
|
|
|
tr_stalled:
|
|
err = USB_ERR_STALLED;
|
|
tr_valid:
|
|
done:
|
|
*plength = len;
|
|
*pptr = ptr;
|
|
return (err);
|
|
}
|
|
|
|
static void
|
|
musbotg_xfer_setup(struct usb_setup_params *parm)
|
|
{
|
|
const struct usb_hw_ep_profile *pf;
|
|
struct musbotg_softc *sc;
|
|
struct usb_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;
|
|
|
|
usbd_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 "usbd_transfer_setup_sub" set an error
|
|
*/
|
|
if (parm->err) {
|
|
return;
|
|
}
|
|
/*
|
|
* allocate transfer descriptors
|
|
*/
|
|
last_obj = NULL;
|
|
|
|
/*
|
|
* get profile stuff
|
|
*/
|
|
if (ntd) {
|
|
|
|
ep_no = xfer->endpointno & UE_ADDR;
|
|
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 usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
musbotg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
|
|
struct usb_endpoint *ep)
|
|
{
|
|
struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus);
|
|
|
|
DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
|
|
ep, udev->address,
|
|
edesc->bEndpointAddress, udev->flags.usb_mode,
|
|
sc->sc_rt_addr);
|
|
|
|
if (udev->device_index != sc->sc_rt_addr) {
|
|
|
|
if (udev->flags.usb_mode != USB_MODE_DEVICE) {
|
|
/* not supported */
|
|
return;
|
|
}
|
|
if ((udev->speed != USB_SPEED_FULL) &&
|
|
(udev->speed != USB_SPEED_HIGH)) {
|
|
/* not supported */
|
|
return;
|
|
}
|
|
switch (edesc->bmAttributes & UE_XFERTYPE) {
|
|
case UE_CONTROL:
|
|
ep->methods = &musbotg_device_ctrl_methods;
|
|
break;
|
|
case UE_INTERRUPT:
|
|
ep->methods = &musbotg_device_intr_methods;
|
|
break;
|
|
case UE_ISOCHRONOUS:
|
|
ep->methods = &musbotg_device_isoc_methods;
|
|
break;
|
|
case UE_BULK:
|
|
ep->methods = &musbotg_device_bulk_methods;
|
|
break;
|
|
default:
|
|
/* do nothing */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
musbotg_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
|
|
{
|
|
struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus);
|
|
|
|
switch (state) {
|
|
case USB_HW_POWER_SUSPEND:
|
|
musbotg_suspend(sc);
|
|
break;
|
|
case USB_HW_POWER_SHUTDOWN:
|
|
musbotg_uninit(sc);
|
|
break;
|
|
case USB_HW_POWER_RESUME:
|
|
musbotg_resume(sc);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct usb_bus_methods musbotg_bus_methods =
|
|
{
|
|
.endpoint_init = &musbotg_ep_init,
|
|
.xfer_setup = &musbotg_xfer_setup,
|
|
.xfer_unsetup = &musbotg_xfer_unsetup,
|
|
.get_hw_ep_profile = &musbotg_get_hw_ep_profile,
|
|
.xfer_stall = &musbotg_xfer_stall,
|
|
.set_stall = &musbotg_set_stall,
|
|
.clear_stall = &musbotg_clear_stall,
|
|
.roothub_exec = &musbotg_roothub_exec,
|
|
.xfer_poll = &musbotg_do_poll,
|
|
.set_hw_power_sleep = &musbotg_set_hw_power_sleep,
|
|
};
|