Index: head/sys/arm/freescale/vybrid/vf_ehci.c
===================================================================
--- head/sys/arm/freescale/vybrid/vf_ehci.c (revision 294988)
+++ head/sys/arm/freescale/vybrid/vf_ehci.c (revision 294989)
@@ -1,420 +1,434 @@
/*-
* Copyright (c) 2013 Ruslan Bukin
* 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.
*/
/*
* Vybrid Family Universal Serial Bus (USB) Controller
* Chapter 44-45, Vybrid Reference Manual, Rev. 5, 07/2013
*/
#include
__FBSDID("$FreeBSD$");
#include "opt_bus.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "gpio_if.h"
#include "opt_platform.h"
#define ENUTMILEVEL3 (1 << 15)
#define ENUTMILEVEL2 (1 << 14)
#define GPIO_USB_PWR 134
#define USB_ID 0x000 /* Identification register */
#define USB_HWGENERAL 0x004 /* Hardware General */
#define USB_HWHOST 0x008 /* Host Hardware Parameters */
#define USB_HWDEVICE 0x00C /* Device Hardware Parameters */
#define USB_HWTXBUF 0x010 /* TX Buffer Hardware Parameters */
#define USB_HWRXBUF 0x014 /* RX Buffer Hardware Parameters */
#define USB_HCSPARAMS 0x104 /* Host Controller Structural Parameters */
#define USBPHY_PWD 0x00 /* PHY Power-Down Register */
#define USBPHY_PWD_SET 0x04 /* PHY Power-Down Register */
#define USBPHY_PWD_CLR 0x08 /* PHY Power-Down Register */
#define USBPHY_PWD_TOG 0x0C /* PHY Power-Down Register */
#define USBPHY_TX 0x10 /* PHY Transmitter Control Register */
#define USBPHY_RX 0x20 /* PHY Receiver Control Register */
#define USBPHY_RX_SET 0x24 /* PHY Receiver Control Register */
#define USBPHY_RX_CLR 0x28 /* PHY Receiver Control Register */
#define USBPHY_RX_TOG 0x2C /* PHY Receiver Control Register */
#define USBPHY_CTRL 0x30 /* PHY General Control Register */
#define USBPHY_CTRL_SET 0x34 /* PHY General Control Register */
#define USBPHY_CTRL_CLR 0x38 /* PHY General Control Register */
#define USBPHY_CTRL_TOG 0x3C /* PHY General Control Register */
#define USBPHY_STATUS 0x40 /* PHY Status Register */
#define USBPHY_DEBUG 0x50 /* PHY Debug Register */
#define USBPHY_DEBUG_SET 0x54 /* PHY Debug Register */
#define USBPHY_DEBUG_CLR 0x58 /* PHY Debug Register */
#define USBPHY_DEBUG_TOG 0x5C /* PHY Debug Register */
#define USBPHY_DEBUG0_STATUS 0x60 /* UTMI Debug Status Register 0 */
#define USBPHY_DEBUG1 0x70 /* UTMI Debug Status Register 1 */
#define USBPHY_DEBUG1_SET 0x74 /* UTMI Debug Status Register 1 */
#define USBPHY_DEBUG1_CLR 0x78 /* UTMI Debug Status Register 1 */
#define USBPHY_DEBUG1_TOG 0x7C /* UTMI Debug Status Register 1 */
#define USBPHY_VERSION 0x80 /* UTMI RTL Version */
#define USBPHY_IP 0x90 /* PHY IP Block Register */
#define USBPHY_IP_SET 0x94 /* PHY IP Block Register */
#define USBPHY_IP_CLR 0x98 /* PHY IP Block Register */
#define USBPHY_IP_TOG 0x9C /* PHY IP Block Register */
#define USBPHY_CTRL_SFTRST (1U << 31)
#define USBPHY_CTRL_CLKGATE (1 << 30)
#define USBPHY_DEBUG_CLKGATE (1 << 30)
#define PHY_READ4(_sc, _reg) \
bus_space_read_4(_sc->bst_phy, _sc->bsh_phy, _reg)
#define PHY_WRITE4(_sc, _reg, _val) \
bus_space_write_4(_sc->bst_phy, _sc->bsh_phy, _reg, _val)
#define USBC_READ4(_sc, _reg) \
bus_space_read_4(_sc->bst_usbc, _sc->bsh_usbc, _reg)
#define USBC_WRITE4(_sc, _reg, _val) \
bus_space_write_4(_sc->bst_usbc, _sc->bsh_usbc, _reg, _val)
/* Forward declarations */
static int vybrid_ehci_attach(device_t dev);
static int vybrid_ehci_detach(device_t dev);
static int vybrid_ehci_probe(device_t dev);
struct vybrid_ehci_softc {
ehci_softc_t base;
device_t dev;
struct resource *res[6];
bus_space_tag_t bst_phy;
bus_space_handle_t bsh_phy;
bus_space_tag_t bst_usbc;
bus_space_handle_t bsh_usbc;
};
static struct resource_spec vybrid_ehci_spec[] = {
{ SYS_RES_MEMORY, 0, RF_ACTIVE },
{ SYS_RES_MEMORY, 1, RF_ACTIVE },
{ SYS_RES_MEMORY, 2, RF_ACTIVE },
{ SYS_RES_IRQ, 0, RF_ACTIVE },
{ -1, 0 }
};
static device_method_t ehci_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, vybrid_ehci_probe),
DEVMETHOD(device_attach, vybrid_ehci_attach),
DEVMETHOD(device_detach, vybrid_ehci_detach),
DEVMETHOD(device_suspend, bus_generic_suspend),
DEVMETHOD(device_resume, bus_generic_resume),
DEVMETHOD(device_shutdown, bus_generic_shutdown),
/* Bus interface */
DEVMETHOD(bus_print_child, bus_generic_print_child),
{ 0, 0 }
};
/* kobj_class definition */
static driver_t ehci_driver = {
"ehci",
ehci_methods,
sizeof(ehci_softc_t)
};
static devclass_t ehci_devclass;
DRIVER_MODULE(ehci, simplebus, ehci_driver, ehci_devclass, 0, 0);
MODULE_DEPEND(ehci, usb, 1, 1, 1);
+static void
+vybrid_ehci_post_reset(struct ehci_softc *ehci_softc)
+{
+ uint32_t usbmode;
+
+ /* Force HOST mode */
+ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_NOLPM);
+ usbmode &= ~EHCI_UM_CM;
+ usbmode |= EHCI_UM_CM_HOST;
+ EOWRITE4(ehci_softc, EHCI_USBMODE_NOLPM, usbmode);
+}
+
/*
* Public methods
*/
static int
vybrid_ehci_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (ofw_bus_is_compatible(dev, "fsl,mvf600-usb-ehci") == 0)
return (ENXIO);
device_set_desc(dev, "Vybrid Family integrated USB controller");
return (BUS_PROBE_DEFAULT);
}
static int
phy_init(struct vybrid_ehci_softc *esc)
{
device_t sc_gpio_dev;
int reg;
/* Reset phy */
reg = PHY_READ4(esc, USBPHY_CTRL);
reg |= (USBPHY_CTRL_SFTRST);
PHY_WRITE4(esc, USBPHY_CTRL, reg);
/* Minimum reset time */
DELAY(10000);
reg &= ~(USBPHY_CTRL_SFTRST | USBPHY_CTRL_CLKGATE);
PHY_WRITE4(esc, USBPHY_CTRL, reg);
reg = (ENUTMILEVEL2 | ENUTMILEVEL3);
PHY_WRITE4(esc, USBPHY_CTRL_SET, reg);
/* Get the GPIO device, we need this to give power to USB */
sc_gpio_dev = devclass_get_device(devclass_find("gpio"), 0);
if (sc_gpio_dev == NULL) {
device_printf(esc->dev, "Error: failed to get the GPIO dev\n");
return (1);
}
/* Give power to USB */
GPIO_PIN_SETFLAGS(sc_gpio_dev, GPIO_USB_PWR, GPIO_PIN_OUTPUT);
GPIO_PIN_SET(sc_gpio_dev, GPIO_USB_PWR, GPIO_PIN_HIGH);
/* Power up PHY */
PHY_WRITE4(esc, USBPHY_PWD, 0x00);
/* Ungate clocks */
reg = PHY_READ4(esc, USBPHY_DEBUG);
reg &= ~(USBPHY_DEBUG_CLKGATE);
PHY_WRITE4(esc, USBPHY_DEBUG, reg);
#if 0
printf("USBPHY_CTRL == 0x%08x\n",
PHY_READ4(esc, USBPHY_CTRL));
printf("USBPHY_IP == 0x%08x\n",
PHY_READ4(esc, USBPHY_IP));
printf("USBPHY_STATUS == 0x%08x\n",
PHY_READ4(esc, USBPHY_STATUS));
printf("USBPHY_DEBUG == 0x%08x\n",
PHY_READ4(esc, USBPHY_DEBUG));
printf("USBPHY_DEBUG0_STATUS == 0x%08x\n",
PHY_READ4(esc, USBPHY_DEBUG0_STATUS));
printf("USBPHY_DEBUG1 == 0x%08x\n",
PHY_READ4(esc, USBPHY_DEBUG1));
#endif
return (0);
}
static int
vybrid_ehci_attach(device_t dev)
{
struct vybrid_ehci_softc *esc;
ehci_softc_t *sc;
bus_space_handle_t bsh;
int err;
int reg;
esc = device_get_softc(dev);
esc->dev = dev;
sc = &esc->base;
sc->sc_bus.parent = dev;
sc->sc_bus.devices = sc->sc_devices;
sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
sc->sc_bus.dma_bits = 32;
if (bus_alloc_resources(dev, vybrid_ehci_spec, esc->res)) {
device_printf(dev, "could not allocate resources\n");
return (ENXIO);
}
/* EHCI registers */
sc->sc_io_tag = rman_get_bustag(esc->res[0]);
bsh = rman_get_bushandle(esc->res[0]);
sc->sc_io_size = rman_get_size(esc->res[0]);
esc->bst_usbc = rman_get_bustag(esc->res[1]);
esc->bsh_usbc = rman_get_bushandle(esc->res[1]);
esc->bst_phy = rman_get_bustag(esc->res[2]);
esc->bsh_phy = rman_get_bushandle(esc->res[2]);
/* get all DMA memory */
if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(dev),
&ehci_iterate_hw_softc))
return (ENXIO);
#if 0
printf("USBx_HCSPARAMS is 0x%08x\n",
bus_space_read_4(sc->sc_io_tag, bsh, USB_HCSPARAMS));
printf("USB_ID == 0x%08x\n",
bus_space_read_4(sc->sc_io_tag, bsh, USB_ID));
printf("USB_HWGENERAL == 0x%08x\n",
bus_space_read_4(sc->sc_io_tag, bsh, USB_HWGENERAL));
printf("USB_HWHOST == 0x%08x\n",
bus_space_read_4(sc->sc_io_tag, bsh, USB_HWHOST));
printf("USB_HWDEVICE == 0x%08x\n",
bus_space_read_4(sc->sc_io_tag, bsh, USB_HWDEVICE));
printf("USB_HWTXBUF == 0x%08x\n",
bus_space_read_4(sc->sc_io_tag, bsh, USB_HWTXBUF));
printf("USB_HWRXBUF == 0x%08x\n",
bus_space_read_4(sc->sc_io_tag, bsh, USB_HWRXBUF));
#endif
if (phy_init(esc)) {
device_printf(dev, "Could not setup PHY\n");
return (1);
}
/*
* Set handle to USB related registers subregion used by
* generic EHCI driver.
*/
err = bus_space_subregion(sc->sc_io_tag, bsh, 0x100,
sc->sc_io_size, &sc->sc_io_hdl);
if (err != 0)
return (ENXIO);
/* Setup interrupt handler */
err = bus_setup_intr(dev, esc->res[3], INTR_TYPE_BIO | INTR_MPSAFE,
NULL, (driver_intr_t *)ehci_interrupt, sc,
&sc->sc_intr_hdl);
if (err) {
device_printf(dev, "Could not setup irq, "
"%d\n", err);
return (1);
}
/* Add USB device */
sc->sc_bus.bdev = device_add_child(dev, "usbus", -1);
if (!sc->sc_bus.bdev) {
device_printf(dev, "Could not add USB device\n");
err = bus_teardown_intr(dev, esc->res[5],
sc->sc_intr_hdl);
if (err)
device_printf(dev, "Could not tear down irq,"
" %d\n", err);
return (1);
}
device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
strlcpy(sc->sc_vendor, "Freescale", sizeof(sc->sc_vendor));
/* Set host mode */
reg = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl, 0xA8);
reg |= 0x3;
bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, 0xA8, reg);
- /* Set flags */
- sc->sc_flags |= EHCI_SCFLG_SETMODE | EHCI_SCFLG_NORESTERM;
+ /* Set flags and callbacks*/
+ sc->sc_flags |= EHCI_SCFLG_TT | EHCI_SCFLG_NORESTERM;
+ sc->sc_vendor_post_reset = vybrid_ehci_post_reset;
+ sc->sc_vendor_get_port_speed = ehci_get_port_speed_portsc;
err = ehci_init(sc);
if (!err) {
sc->sc_flags |= EHCI_SCFLG_DONEINIT;
err = device_probe_and_attach(sc->sc_bus.bdev);
} else {
device_printf(dev, "USB init failed err=%d\n", err);
device_delete_child(dev, sc->sc_bus.bdev);
sc->sc_bus.bdev = NULL;
err = bus_teardown_intr(dev, esc->res[5],
sc->sc_intr_hdl);
if (err)
device_printf(dev, "Could not tear down irq,"
" %d\n", err);
return (1);
}
return (0);
}
static int
vybrid_ehci_detach(device_t dev)
{
struct vybrid_ehci_softc *esc;
ehci_softc_t *sc;
int err;
esc = device_get_softc(dev);
sc = &esc->base;
if (sc->sc_flags & EHCI_SCFLG_DONEINIT)
return (0);
/*
* only call ehci_detach() after ehci_init()
*/
if (sc->sc_flags & EHCI_SCFLG_DONEINIT) {
ehci_detach(sc);
sc->sc_flags &= ~EHCI_SCFLG_DONEINIT;
}
/*
* Disable interrupts that might have been switched on in
* ehci_init.
*/
if (sc->sc_io_tag && sc->sc_io_hdl)
bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl,
EHCI_USBINTR, 0);
if (esc->res[5] && sc->sc_intr_hdl) {
err = bus_teardown_intr(dev, esc->res[5],
sc->sc_intr_hdl);
if (err) {
device_printf(dev, "Could not tear down irq,"
" %d\n", err);
return (err);
}
sc->sc_intr_hdl = NULL;
}
if (sc->sc_bus.bdev) {
device_delete_child(dev, sc->sc_bus.bdev);
sc->sc_bus.bdev = NULL;
}
/* During module unload there are lots of children leftover */
device_delete_children(dev);
bus_release_resources(dev, vybrid_ehci_spec, esc->res);
return (0);
}
Index: head/sys/arm/xilinx/zy7_ehci.c
===================================================================
--- head/sys/arm/xilinx/zy7_ehci.c (revision 294988)
+++ head/sys/arm/xilinx/zy7_ehci.c (revision 294989)
@@ -1,366 +1,379 @@
/*-
* Copyright (c) 2012-2013 Thomas Skibo
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
/*
* A host-controller driver for Zynq-7000's USB OTG controller.
*
* Reference: Zynq-7000 All Programmable SoC Technical Reference Manual.
* (v1.4) November 16, 2012. Xilinx doc UG585. Ch. 15 covers the USB
* controller and register definitions are in appendix B.34.
*/
#include
__FBSDID("$FreeBSD$");
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* Register definitions. */
#define ZY7_USB_ID 0x0000
#define ZY7_USB_HWGENERAL 0x0004
#define ZY7_USB_HWHOST 0x0008
#define ZY7_USB_HWDEVICE 0x000c
#define ZY7_USB_HWTXBUF 0x0010
#define ZY7_USB_HWRXBUF 0x0014
#define ZY7_USB_GPTIMER0LD 0x0080
#define ZY7_USB_GPTIMER0CTRL 0x0084
#define ZY7_USB_GPTIMER1LD 0x0088
#define ZY7_USB_GPTIMER1CTRL 0x008c
#define ZY7_USB_SBUSCFG 0x0090
#define ZY7_USB_CAPLENGTH_HCIVERSION 0x0100
#define ZY7_USB_HCSPARAMS 0x0104
#define ZY7_USB_HCCPARAMS 0x0108
#define ZY7_USB_DCIVERSION 0x0120
#define ZY7_USB_DCCPARAMS 0x0124
#define ZY7_USB_USBCMD 0x0140
#define ZY7_USB_USBSTS 0x0144
#define ZY7_USB_USBINTR 0x0148
#define ZY7_USB_FRINDEX 0x014c
#define ZY7_USB_PERIODICLISTBASE_DEICEADDR 0x0154
#define ZY7_USB_ASYNCLISTADDR_ENDPOINTLISTADDR 0x0158
#define ZY7_USB_TTCTRL 0x015c
#define ZY7_USB_BURSTSIZE 0x0160
#define ZY7_USB_TXFILLTUNING 0x0164
#define ZY7_USB_TXFILLTUNING_TXFIFOTHRES_SHFT 16
#define ZY7_USB_TXFILLTUNING_TXFIFOTHRES_MASK (0x3f<<16)
#define ZY7_USB_TXTFILLTUNING 0x0168
#define ZY7_USB_IC_USB 0x016c
#define ZY7_USB_ULPI_VIEWPORT 0x0170
#define ZY7_USB_ULPI_VIEWPORT_WU (1<<31)
#define ZY7_USB_ULPI_VIEWPORT_RUN (1<<30)
#define ZY7_USB_ULPI_VIEWPORT_RW (1<<29)
#define ZY7_USB_ULPI_VIEWPORT_SS (1<<27)
#define ZY7_USB_ULPI_VIEWPORT_PORT_MASK (7<<24)
#define ZY7_USB_ULPI_VIEWPORT_PORT_SHIFT 24
#define ZY7_USB_ULPI_VIEWPORT_ADDR_MASK (0xff<<16)
#define ZY7_USB_ULPI_VIEWPORT_ADDR_SHIFT 16
#define ZY7_USB_ULPI_VIEWPORT_DATARD_MASK (0xff<<8)
#define ZY7_USB_ULPI_VIEWPORT_DATARD_SHIFT 8
#define ZY7_USB_ULPI_VIEWPORT_DATAWR_MASK (0xff<<0)
#define ZY7_USB_ULPI_VIEWPORT_DATAWR_SHIFT 0
#define ZY7_USB_ENDPTNAK 0x0178
#define ZY7_USB_ENDPTNAKEN 0x017c
#define ZY7_USB_CONFIGFLAG 0x0180
#define ZY7_USB_PORTSC(n) (0x0180+4*(n))
#define ZY7_USB_PORTSC_PTS_MASK (3<<30)
#define ZY7_USB_PORTSC_PTS_SHIFT 30
#define ZY7_USB_PORTSC_PTS_UTMI (0<<30)
#define ZY7_USB_PORTSC_PTS_ULPI (2<<30)
#define ZY7_USB_PORTSC_PTS_SERIAL (3<<30)
#define ZY7_USB_PORTSC_PTW (1<<28)
#define ZY7_USB_PORTSC_PTS2 (1<<25)
#define ZY7_USB_OTGSC 0x01a4
#define ZY7_USB_USBMODE 0x01a8
#define ZY7_USB_ENDPTSETUPSTAT 0x01ac
#define ZY7_USB_ENDPTPRIME 0x01b0
#define ZY7_USB_ENDPTFLUSH 0x01b4
#define ZY7_USB_ENDPTSTAT 0x01b8
#define ZY7_USB_ENDPTCOMPLETE 0x01bc
#define ZY7_USB_ENDPTCTRL(n) (0x01c0+4*(n))
#define EHCI_REG_OFFSET ZY7_USB_CAPLENGTH_HCIVERSION
#define EHCI_REG_SIZE 0x100
+static void
+zy7_ehci_post_reset(struct ehci_softc *ehci_softc)
+{
+ uint32_t usbmode;
+
+ /* Force HOST mode */
+ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_NOLPM);
+ usbmode &= ~EHCI_UM_CM;
+ usbmode |= EHCI_UM_CM_HOST;
+ EOWRITE4(ehci_softc, EHCI_USBMODE_NOLPM, usbmode);
+}
+
static int
zy7_phy_config(device_t dev, bus_space_tag_t io_tag, bus_space_handle_t bsh)
{
phandle_t node;
char buf[64];
uint32_t portsc;
int tries;
node = ofw_bus_get_node(dev);
if (OF_getprop(node, "phy_type", buf, sizeof(buf)) > 0) {
portsc = bus_space_read_4(io_tag, bsh, ZY7_USB_PORTSC(1));
portsc &= ~(ZY7_USB_PORTSC_PTS_MASK | ZY7_USB_PORTSC_PTW |
ZY7_USB_PORTSC_PTS2);
if (strcmp(buf,"ulpi") == 0)
portsc |= ZY7_USB_PORTSC_PTS_ULPI;
else if (strcmp(buf,"utmi") == 0)
portsc |= ZY7_USB_PORTSC_PTS_UTMI;
else if (strcmp(buf,"utmi-wide") == 0)
portsc |= (ZY7_USB_PORTSC_PTS_UTMI |
ZY7_USB_PORTSC_PTW);
else if (strcmp(buf, "serial") == 0)
portsc |= ZY7_USB_PORTSC_PTS_SERIAL;
bus_space_write_4(io_tag, bsh, ZY7_USB_PORTSC(1), portsc);
}
if (OF_getprop(node, "phy_vbus_ext", buf, sizeof(buf)) >= 0) {
/* Tell PHY that VBUS is supplied externally. */
bus_space_write_4(io_tag, bsh, ZY7_USB_ULPI_VIEWPORT,
ZY7_USB_ULPI_VIEWPORT_RUN |
ZY7_USB_ULPI_VIEWPORT_RW |
(0 << ZY7_USB_ULPI_VIEWPORT_PORT_SHIFT) |
(0x0b << ZY7_USB_ULPI_VIEWPORT_ADDR_SHIFT) |
(0x60 << ZY7_USB_ULPI_VIEWPORT_DATAWR_SHIFT)
);
tries = 100;
while ((bus_space_read_4(io_tag, bsh, ZY7_USB_ULPI_VIEWPORT) &
ZY7_USB_ULPI_VIEWPORT_RUN) != 0) {
if (--tries < 0)
return (-1);
DELAY(1);
}
}
return (0);
}
static int
zy7_ehci_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (!ofw_bus_is_compatible(dev, "xlnx,zy7_ehci"))
return (ENXIO);
device_set_desc(dev, "Zynq-7000 EHCI USB 2.0 controller");
return (0);
}
static int zy7_ehci_detach(device_t dev);
static int
zy7_ehci_attach(device_t dev)
{
ehci_softc_t *sc = device_get_softc(dev);
bus_space_handle_t bsh;
int err, rid;
/* initialize some bus fields */
sc->sc_bus.parent = dev;
sc->sc_bus.devices = sc->sc_devices;
sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
sc->sc_bus.dma_bits = 32;
/* get all DMA memory */
if (usb_bus_mem_alloc_all(&sc->sc_bus,
USB_GET_DMA_TAG(dev), &ehci_iterate_hw_softc))
return (ENOMEM);
/* Allocate memory. */
rid = 0;
sc->sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&rid, RF_ACTIVE);
if (sc->sc_io_res == NULL) {
device_printf(dev, "Can't allocate memory");
zy7_ehci_detach(dev);
return (ENOMEM);
}
sc->sc_io_tag = rman_get_bustag(sc->sc_io_res);
bsh = rman_get_bushandle(sc->sc_io_res);
sc->sc_io_size = EHCI_REG_SIZE;
if (bus_space_subregion(sc->sc_io_tag, bsh, EHCI_REG_OFFSET,
sc->sc_io_size, &sc->sc_io_hdl) != 0)
panic("%s: unable to subregion USB host registers",
device_get_name(dev));
/* Allocate IRQ. */
rid = 0;
sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
RF_ACTIVE);
if (sc->sc_irq_res == NULL) {
device_printf(dev, "Can't allocate IRQ\n");
zy7_ehci_detach(dev);
return (ENOMEM);
}
/* Add USB device */
sc->sc_bus.bdev = device_add_child(dev, "usbus", -1);
if (!sc->sc_bus.bdev) {
device_printf(dev, "Could not add USB device\n");
zy7_ehci_detach(dev);
return (ENXIO);
}
device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
device_set_desc(sc->sc_bus.bdev, "Zynq-7000 ehci USB 2.0 controller");
strcpy(sc->sc_vendor, "Xilinx"); /* or IP vendor? */
/* Activate the interrupt */
err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
NULL, (driver_intr_t *)ehci_interrupt, sc,
&sc->sc_intr_hdl);
if (err) {
device_printf(dev, "Cannot setup IRQ\n");
zy7_ehci_detach(dev);
return (err);
}
/* Customization. */
- sc->sc_flags |= EHCI_SCFLG_SETMODE | EHCI_SCFLG_TT |
- EHCI_SCFLG_NORESTERM;
+ sc->sc_flags |= EHCI_SCFLG_TT | EHCI_SCFLG_NORESTERM;
+ sc->sc_vendor_post_reset = zy7_ehci_post_reset;
+ sc->sc_vendor_get_port_speed = ehci_get_port_speed_portsc;
/* Modify FIFO burst threshold from 2 to 8. */
bus_space_write_4(sc->sc_io_tag, bsh,
ZY7_USB_TXFILLTUNING,
8 << ZY7_USB_TXFILLTUNING_TXFIFOTHRES_SHFT);
/* Handle PHY options. */
if (zy7_phy_config(dev, sc->sc_io_tag, bsh) < 0) {
device_printf(dev, "Cannot config phy!\n");
zy7_ehci_detach(dev);
return (EIO);
}
/* Init ehci. */
err = ehci_init(sc);
if (!err) {
sc->sc_flags |= EHCI_SCFLG_DONEINIT;
err = device_probe_and_attach(sc->sc_bus.bdev);
}
if (err) {
device_printf(dev, "USB init failed err=%d\n", err);
zy7_ehci_detach(dev);
return (err);
}
return (0);
}
static int
zy7_ehci_detach(device_t dev)
{
ehci_softc_t *sc = device_get_softc(dev);
sc->sc_flags &= ~EHCI_SCFLG_DONEINIT;
if (device_is_attached(dev))
bus_generic_detach(dev);
if (sc->sc_irq_res && sc->sc_intr_hdl)
/* call ehci_detach() after ehci_init() called after
* successful bus_setup_intr().
*/
ehci_detach(sc);
if (sc->sc_bus.bdev) {
device_detach(sc->sc_bus.bdev);
device_delete_child(dev, sc->sc_bus.bdev);
}
if (sc->sc_irq_res) {
if (sc->sc_intr_hdl != NULL)
bus_teardown_intr(dev, sc->sc_irq_res,
sc->sc_intr_hdl);
bus_release_resource(dev, SYS_RES_IRQ,
rman_get_rid(sc->sc_irq_res), sc->sc_irq_res);
}
if (sc->sc_io_res)
bus_release_resource(dev, SYS_RES_MEMORY,
rman_get_rid(sc->sc_io_res), sc->sc_io_res);
usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc);
return (0);
}
static device_method_t ehci_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, zy7_ehci_probe),
DEVMETHOD(device_attach, zy7_ehci_attach),
DEVMETHOD(device_detach, zy7_ehci_detach),
DEVMETHOD(device_suspend, bus_generic_suspend),
DEVMETHOD(device_resume, bus_generic_resume),
DEVMETHOD(device_shutdown, bus_generic_shutdown),
/* Bus interface */
DEVMETHOD(bus_print_child, bus_generic_print_child),
DEVMETHOD_END
};
static driver_t ehci_driver = {
"ehci",
ehci_methods,
sizeof(struct ehci_softc),
};
static devclass_t ehci_devclass;
DRIVER_MODULE(ehci, simplebus, ehci_driver, ehci_devclass, NULL, NULL);
MODULE_DEPEND(ehci, usb, 1, 1, 1);
Index: head/sys/dev/usb/controller/ehci.c
===================================================================
--- head/sys/dev/usb/controller/ehci.c (revision 294988)
+++ head/sys/dev/usb/controller/ehci.c (revision 294989)
@@ -1,3958 +1,3974 @@
/* $FreeBSD$ */
/*-
* 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
*/
#ifdef USB_GLOBAL_INCLUDE_FILE
#include USB_GLOBAL_INCLUDE_FILE
#else
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define USB_DEBUG_VAR ehcidebug
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#endif /* USB_GLOBAL_INCLUDE_FILE */
#include
#include
#define EHCI_BUS2SC(bus) \
((ehci_softc_t *)(((uint8_t *)(bus)) - \
((uint8_t *)&(((ehci_softc_t *)0)->sc_bus))))
#ifdef USB_DEBUG
static int ehcidebug = 0;
static int ehcinohighspeed = 0;
static int ehciiaadbug = 0;
static int ehcilostintrbug = 0;
static SYSCTL_NODE(_hw_usb, OID_AUTO, ehci, CTLFLAG_RW, 0, "USB ehci");
SYSCTL_INT(_hw_usb_ehci, OID_AUTO, debug, CTLFLAG_RWTUN,
&ehcidebug, 0, "Debug level");
SYSCTL_INT(_hw_usb_ehci, OID_AUTO, no_hs, CTLFLAG_RWTUN,
&ehcinohighspeed, 0, "Disable High Speed USB");
SYSCTL_INT(_hw_usb_ehci, OID_AUTO, iaadbug, CTLFLAG_RWTUN,
&ehciiaadbug, 0, "Enable doorbell bug workaround");
SYSCTL_INT(_hw_usb_ehci, OID_AUTO, lostintrbug, CTLFLAG_RWTUN,
&ehcilostintrbug, 0, "Enable lost interrupt bug workaround");
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
static const struct usb_bus_methods ehci_bus_methods;
static const struct usb_pipe_methods ehci_device_bulk_methods;
static const struct usb_pipe_methods ehci_device_ctrl_methods;
static const struct usb_pipe_methods ehci_device_intr_methods;
static const struct usb_pipe_methods ehci_device_isoc_fs_methods;
static const struct usb_pipe_methods ehci_device_isoc_hs_methods;
static void ehci_do_poll(struct usb_bus *);
static void ehci_device_done(struct usb_xfer *, usb_error_t);
static uint8_t ehci_check_transfer(struct usb_xfer *);
static void ehci_timeout(void *);
static void ehci_poll_timeout(void *);
static void ehci_root_intr(ehci_softc_t *sc);
struct ehci_std_temp {
ehci_softc_t *sc;
struct usb_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 last_frame;
};
void
ehci_iterate_hw_softc(struct usb_bus *bus, usb_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.terminate_pc, &sc->sc_hw.terminate_pg,
sizeof(struct ehci_qh_sub), EHCI_QH_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);
}
}
usb_error_t
ehci_reset(ehci_softc_t *sc)
{
uint32_t hcr;
int i;
EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET);
for (i = 0; i < 100; i++) {
usb_pause_mtx(NULL, hz / 128);
hcr = EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_HCRESET;
if (!hcr) {
- if (sc->sc_flags & (EHCI_SCFLG_SETMODE | EHCI_SCFLG_BIGEMMIO)) {
- /*
- * Force USBMODE as requested. Controllers
- * may have multiple operating modes.
- */
- uint32_t usbmode = EOREAD4(sc, EHCI_USBMODE);
- if (sc->sc_flags & EHCI_SCFLG_SETMODE) {
- usbmode = (usbmode &~ EHCI_UM_CM) | EHCI_UM_CM_HOST;
- device_printf(sc->sc_bus.bdev,
- "set host controller mode\n");
- }
- if (sc->sc_flags & EHCI_SCFLG_BIGEMMIO) {
- usbmode = (usbmode &~ EHCI_UM_ES) | EHCI_UM_ES_BE;
- device_printf(sc->sc_bus.bdev,
- "set big-endian mode\n");
- }
- EOWRITE4(sc, EHCI_USBMODE, usbmode);
- }
+ if (sc->sc_vendor_post_reset != NULL)
+ sc->sc_vendor_post_reset(sc);
return (0);
}
}
device_printf(sc->sc_bus.bdev, "reset timeout\n");
return (USB_ERR_IOERROR);
}
static usb_error_t
ehci_hcreset(ehci_softc_t *sc)
{
uint32_t hcr;
int i;
EOWRITE4(sc, EHCI_USBCMD, 0); /* Halt controller */
for (i = 0; i < 100; i++) {
usb_pause_mtx(NULL, hz / 128);
hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH;
if (hcr)
break;
}
if (!hcr)
/*
* Fall through and try reset anyway even though
* Table 2-9 in the EHCI spec says this will result
* in undefined behavior.
*/
device_printf(sc->sc_bus.bdev, "stop timeout\n");
return (ehci_reset(sc));
}
static int
ehci_init_sub(struct ehci_softc *sc)
{
struct usb_page_search buf_res;
uint32_t cparams;
uint32_t hcr;
uint8_t i;
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 */
EOWRITE4(sc, EHCI_CTRLDSSEGMENT, 0);
}
usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
EOWRITE4(sc, EHCI_PERIODICLISTBASE, buf_res.physaddr);
usbd_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res);
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++) {
usb_pause_mtx(NULL, hz / 128);
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);
}
return (USB_ERR_NORMAL_COMPLETION);
}
usb_error_t
ehci_init(ehci_softc_t *sc)
{
struct usb_page_search buf_res;
uint32_t version;
uint32_t sparams;
uint16_t i;
uint16_t x;
uint16_t y;
uint16_t bit;
usb_error_t err = 0;
DPRINTF("start\n");
usb_callout_init_mtx(&sc->sc_tmo_pcd, &sc->sc_bus.bus_mtx, 0);
usb_callout_init_mtx(&sc->sc_tmo_poll, &sc->sc_bus.bus_mtx, 0);
sc->sc_offs = EHCI_CAPLENGTH(EREAD4(sc, EHCI_CAPLEN_HCIVERSION));
#ifdef USB_DEBUG
if (ehciiaadbug)
sc->sc_flags |= EHCI_SCFLG_IAADBUG;
if (ehcilostintrbug)
sc->sc_flags |= EHCI_SCFLG_LOSTINTRBUG;
if (ehcidebug > 2) {
ehci_dump_regs(sc);
}
#endif
version = EHCI_HCIVERSION(EREAD4(sc, EHCI_CAPLEN_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);
sc->sc_bus.usbrev = USB_REV_2_0;
if (!(sc->sc_flags & EHCI_SCFLG_DONTRESET)) {
/* Reset the controller */
DPRINTF("%s: resetting\n",
device_get_nameunit(sc->sc_bus.bdev));
err = ehci_hcreset(sc);
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;
if (1) {
struct ehci_qh_sub *qh;
usbd_get_page(&sc->sc_hw.terminate_pc, 0, &buf_res);
qh = buf_res.buffer;
sc->sc_terminate_self = htohc32(sc, buf_res.physaddr);
/* init terminate TD */
qh->qtd_next =
htohc32(sc, EHCI_LINK_TERMINATE);
qh->qtd_altnext =
htohc32(sc, EHCI_LINK_TERMINATE);
qh->qtd_status =
htohc32(sc, EHCI_QTD_HALTED);
}
for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
ehci_qh_t *qh;
usbd_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 =
htohc32(sc, buf_res.physaddr) |
htohc32(sc, EHCI_LINK_QH);
qh->qh_endp =
htohc32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH));
qh->qh_endphub =
htohc32(sc, EHCI_QH_SET_MULT(1));
qh->qh_curqtd = 0;
qh->qh_qtd.qtd_next =
htohc32(sc, EHCI_LINK_TERMINATE);
qh->qh_qtd.qtd_altnext =
htohc32(sc, EHCI_LINK_TERMINATE);
qh->qh_qtd.qtd_status =
htohc32(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 = htohc32(sc, EHCI_LINK_TERMINATE);
}
for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
ehci_sitd_t *sitd;
ehci_itd_t *itd;
usbd_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 =
htohc32(sc, buf_res.physaddr) |
htohc32(sc, EHCI_LINK_SITD);
sitd->sitd_back =
htohc32(sc, EHCI_LINK_TERMINATE);
sitd->sitd_next =
sc->sc_intr_p_last[i | (EHCI_VIRTUAL_FRAMELIST_COUNT / 2)]->qh_self;
usbd_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 =
htohc32(sc, buf_res.physaddr) |
htohc32(sc, EHCI_LINK_ITD);
itd->itd_next =
sitd->sitd_self;
}
usbd_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;
}
}
usbd_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 =
htohc32(sc, buf_res.physaddr) |
htohc32(sc, EHCI_LINK_QH);
/* fill the QH */
qh->qh_endp =
htohc32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | EHCI_QH_HRECL);
qh->qh_endphub = htohc32(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 = htohc32(sc, EHCI_LINK_TERMINATE);
qh->qh_qtd.qtd_altnext = htohc32(sc, EHCI_LINK_TERMINATE);
qh->qh_qtd.qtd_status = htohc32(sc, EHCI_QTD_HALTED);
}
/* flush all cache into memory */
usb_bus_mem_flush_all(&sc->sc_bus, &ehci_iterate_hw_softc);
#ifdef USB_DEBUG
if (ehcidebug) {
ehci_dump_sqh(sc, sc->sc_async_p_last);
}
#endif
/* finial setup */
err = ehci_init_sub(sc);
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);
usb_callout_stop(&sc->sc_tmo_pcd);
usb_callout_stop(&sc->sc_tmo_poll);
EOWRITE4(sc, EHCI_USBINTR, 0);
USB_BUS_UNLOCK(&sc->sc_bus);
if (ehci_hcreset(sc)) {
DPRINTF("reset failed!\n");
}
/* XXX let stray task complete */
usb_pause_mtx(NULL, hz / 20);
usb_callout_drain(&sc->sc_tmo_pcd);
usb_callout_drain(&sc->sc_tmo_poll);
}
static void
ehci_suspend(ehci_softc_t *sc)
{
DPRINTF("stopping the HC\n");
/* reset HC */
ehci_hcreset(sc);
}
static void
ehci_resume(ehci_softc_t *sc)
{
/* reset HC */
ehci_hcreset(sc);
/* setup HC */
ehci_init_sub(sc);
/* catch any lost interrupts */
ehci_do_poll(&sc->sc_bus);
}
#ifdef 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 = hc32toh(sc, link);
printf("0x%08x", link);
if (link & EHCI_LINK_TERMINATE)
printf("");
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 = hc32toh(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,
hc32toh(sc, qtd->qtd_buffer[s]));
}
for (s = 0; s < 5; s++) {
printf(" buffer_hi[%d]=0x%08x\n", s,
hc32toh(sc, qtd->qtd_buffer_hi[s]));
}
}
static uint8_t
ehci_dump_sqtd(ehci_softc_t *sc, ehci_qtd_t *sqtd)
{
uint8_t temp;
usb_pc_cpu_invalidate(sqtd->page_cache);
printf("QTD(%p) at 0x%08x:\n", sqtd, hc32toh(sc, sqtd->qtd_self));
ehci_dump_qtd(sc, sqtd);
temp = (sqtd->qtd_next & htohc32(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;
usb_pc_cpu_invalidate(qh->page_cache);
printf("QH(%p) at 0x%08x:\n", qh, hc32toh(sc, qh->qh_self) & ~0x1F);
printf(" link=");
ehci_dump_link(sc, qh->qh_link, 1);
printf("\n");
endp = hc32toh(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 = hc32toh(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)
{
usb_pc_cpu_invalidate(sitd->page_cache);
printf("SITD(%p) at 0x%08x\n", sitd, hc32toh(sc, sitd->sitd_self) & ~0x1F);
printf(" next=0x%08x\n", hc32toh(sc, sitd->sitd_next));
printf(" portaddr=0x%08x dir=%s addr=%d endpt=0x%x port=0x%x huba=0x%x\n",
hc32toh(sc, sitd->sitd_portaddr),
(sitd->sitd_portaddr & htohc32(sc, EHCI_SITD_SET_DIR_IN))
? "in" : "out",
EHCI_SITD_GET_ADDR(hc32toh(sc, sitd->sitd_portaddr)),
EHCI_SITD_GET_ENDPT(hc32toh(sc, sitd->sitd_portaddr)),
EHCI_SITD_GET_PORT(hc32toh(sc, sitd->sitd_portaddr)),
EHCI_SITD_GET_HUBA(hc32toh(sc, sitd->sitd_portaddr)));
printf(" mask=0x%08x\n", hc32toh(sc, sitd->sitd_mask));
printf(" status=0x%08x <%s> len=0x%x\n", hc32toh(sc, sitd->sitd_status),
(sitd->sitd_status & htohc32(sc, EHCI_SITD_ACTIVE)) ? "ACTIVE" : "",
EHCI_SITD_GET_LEN(hc32toh(sc, sitd->sitd_status)));
printf(" back=0x%08x, bp=0x%08x,0x%08x,0x%08x,0x%08x\n",
hc32toh(sc, sitd->sitd_back),
hc32toh(sc, sitd->sitd_bp[0]),
hc32toh(sc, sitd->sitd_bp[1]),
hc32toh(sc, sitd->sitd_bp_hi[0]),
hc32toh(sc, sitd->sitd_bp_hi[1]));
}
static void
ehci_dump_itd(ehci_softc_t *sc, ehci_itd_t *itd)
{
usb_pc_cpu_invalidate(itd->page_cache);
printf("ITD(%p) at 0x%08x\n", itd, hc32toh(sc, itd->itd_self) & ~0x1F);
printf(" next=0x%08x\n", hc32toh(sc, itd->itd_next));
printf(" status[0]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[0]),
(itd->itd_status[0] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
printf(" status[1]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[1]),
(itd->itd_status[1] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
printf(" status[2]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[2]),
(itd->itd_status[2] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
printf(" status[3]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[3]),
(itd->itd_status[3] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
printf(" status[4]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[4]),
(itd->itd_status[4] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
printf(" status[5]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[5]),
(itd->itd_status[5] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
printf(" status[6]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[6]),
(itd->itd_status[6] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
printf(" status[7]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[7]),
(itd->itd_status[7] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
printf(" bp[0]=0x%08x\n", hc32toh(sc, itd->itd_bp[0]));
printf(" addr=0x%02x; endpt=0x%01x\n",
EHCI_ITD_GET_ADDR(hc32toh(sc, itd->itd_bp[0])),
EHCI_ITD_GET_ENDPT(hc32toh(sc, itd->itd_bp[0])));
printf(" bp[1]=0x%08x\n", hc32toh(sc, itd->itd_bp[1]));
printf(" dir=%s; mpl=0x%02x\n",
(hc32toh(sc, itd->itd_bp[1]) & EHCI_ITD_SET_DIR_IN) ? "in" : "out",
EHCI_ITD_GET_MPL(hc32toh(sc, itd->itd_bp[1])));
printf(" bp[2..6]=0x%08x,0x%08x,0x%08x,0x%08x,0x%08x\n",
hc32toh(sc, itd->itd_bp[2]),
hc32toh(sc, itd->itd_bp[3]),
hc32toh(sc, itd->itd_bp[4]),
hc32toh(sc, itd->itd_bp[5]),
hc32toh(sc, itd->itd_bp[6]));
printf(" bp_hi=0x%08x,0x%08x,0x%08x,0x%08x,\n"
" 0x%08x,0x%08x,0x%08x\n",
hc32toh(sc, itd->itd_bp_hi[0]),
hc32toh(sc, itd->itd_bp_hi[1]),
hc32toh(sc, itd->itd_bp_hi[2]),
hc32toh(sc, itd->itd_bp_hi[3]),
hc32toh(sc, itd->itd_bp_hi[4]),
hc32toh(sc, itd->itd_bp_hi[5]),
hc32toh(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 usb_xfer *xfer)
{
/* check for early completion */
if (ehci_check_transfer(xfer)) {
return;
}
/* 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, &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;
usb_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;
usb_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;
usb_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;
usb_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;
usb_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;
usb_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;
usb_pc_cpu_flush(std->prev->page_cache);
if (std->next) {
std->next->prev = std->prev;
usb_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;
usb_pc_cpu_flush(std->prev->page_cache);
if (std->next) {
std->next->prev = std->prev;
usb_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;
usb_pc_cpu_flush(sqh->prev->page_cache);
if (sqh->next) {
sqh->next->prev = sqh->prev;
usb_pc_cpu_flush(sqh->next->page_cache);
}
last = ((last == sqh) ? sqh->prev : last);
sqh->prev = 0;
usb_pc_cpu_flush(sqh->page_cache);
}
return (last);
}
static void
ehci_data_toggle_update(struct usb_xfer *xfer, uint16_t actlen, uint16_t xlen)
{
uint16_t rem;
uint8_t dt;
/* count number of full packets */
dt = (actlen / xfer->max_packet_size) & 1;
/* compute remainder */
rem = actlen % xfer->max_packet_size;
if (rem > 0)
dt ^= 1; /* short packet at the end */
else if (actlen != xlen)
dt ^= 1; /* zero length packet at the end */
else if (xlen == 0)
dt ^= 1; /* zero length transfer */
xfer->endpoint->toggle_next ^= dt;
}
static usb_error_t
ehci_non_isoc_done_sub(struct usb_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) {
usbd_xfer_set_frame_len(xfer, xfer->aframes, 0);
}
while (1) {
usb_pc_cpu_invalidate(td->page_cache);
status = hc32toh(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;
/* manually update data toggle */
ehci_data_toggle_update(xfer, td->len - len, td->len);
}
/* Check for last transfer */
if (((void *)td) == xfer->td_transfer_last) {
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;
#ifdef 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->endpointno, 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
if (status & EHCI_QTD_HALTED) {
if ((xfer->xroot->udev->parent_hs_hub != NULL) ||
(xfer->xroot->udev->address != 0)) {
/* try to separate I/O errors from STALL */
if (EHCI_QTD_GET_CERR(status) == 0)
return (USB_ERR_IOERROR);
}
return (USB_ERR_STALLED);
}
return (USB_ERR_NORMAL_COMPLETION);
}
static void
ehci_non_isoc_done(struct usb_xfer *xfer)
{
ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
ehci_qh_t *qh;
uint32_t status;
usb_error_t err = 0;
DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
xfer, xfer->endpoint);
#ifdef USB_DEBUG
if (ehcidebug > 10) {
ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
ehci_dump_sqtds(sc, xfer->td_transfer_first);
}
#endif
/* extract data toggle directly from the QH's overlay area */
qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
usb_pc_cpu_invalidate(qh->page_cache);
status = hc32toh(sc, qh->qh_qtd.qtd_status);
/* 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 usb_xfer *xfer)
{
const struct usb_pipe_methods *methods = xfer->endpoint->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;
usb_pc_cpu_invalidate(td->page_cache);
status = hc32toh(sc, td->sitd_status);
/* also check if first is complete */
td = xfer->td_transfer_first;
usb_pc_cpu_invalidate(td->page_cache);
status |= hc32toh(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 */
/* check last transfer */
td = xfer->td_transfer_last;
usb_pc_cpu_invalidate(td->page_cache);
status = td->itd_status[0];
status |= td->itd_status[1];
status |= td->itd_status[2];
status |= td->itd_status[3];
status |= td->itd_status[4];
status |= td->itd_status[5];
status |= td->itd_status[6];
status |= td->itd_status[7];
/* also check first transfer */
td = xfer->td_transfer_first;
usb_pc_cpu_invalidate(td->page_cache);
status |= td->itd_status[0];
status |= td->itd_status[1];
status |= td->itd_status[2];
status |= td->itd_status[3];
status |= td->itd_status[4];
status |= td->itd_status[5];
status |= td->itd_status[6];
status |= td->itd_status[7];
/* if no transactions are active we continue */
if (!(status & htohc32(sc, EHCI_ITD_ACTIVE))) {
ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
goto transferred;
}
} else {
ehci_qtd_t *td;
ehci_qh_t *qh;
/* 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;
qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
usb_pc_cpu_invalidate(qh->page_cache);
status = hc32toh(sc, qh->qh_qtd.qtd_status);
if (status & EHCI_QTD_ACTIVE) {
/* transfer is pending */
goto done;
}
while (1) {
usb_pc_cpu_invalidate(td->page_cache);
status = hc32toh(sc, td->qtd_status);
/*
* Check if there is an active TD which
* indicates that 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);
ehci_root_intr(sc);
}
static void
ehci_interrupt_poll(ehci_softc_t *sc)
{
struct usb_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;
}
}
}
/*
* Some EHCI chips from VIA / ATI seem to trigger interrupts before
* writing back the qTD status, or miss signalling occasionally under
* heavy load. If the host machine is too fast, we can miss
* transaction completion - when we scan the active list the
* transaction still seems to be active. This generally exhibits
* itself as a umass stall that never recovers.
*
* We work around this behaviour by setting up this callback after any
* softintr that completes with transactions still pending, giving us
* another chance to check for completion after the writeback has
* taken place.
*/
static void
ehci_poll_timeout(void *arg)
{
ehci_softc_t *sc = arg;
DPRINTFN(3, "\n");
ehci_interrupt_poll(sc);
}
/*------------------------------------------------------------------------*
* 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");
#ifdef 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__);
#ifdef 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);
ehci_root_intr(sc);
/* do not allow RHSC interrupts > 1 per second */
usb_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);
if (sc->sc_flags & EHCI_SCFLG_LOSTINTRBUG) {
usb_callout_reset(&sc->sc_tmo_poll, hz / 128,
(void *)&ehci_poll_timeout, sc);
}
done:
USB_BUS_UNLOCK(&sc->sc_bus);
}
/*
* called when a request does not complete
*/
static void
ehci_timeout(void *arg)
{
struct usb_xfer *xfer = arg;
DPRINTF("xfer=%p\n", xfer);
USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
/* transfer is transferred */
ehci_device_done(xfer, USB_ERR_TIMEOUT);
}
static void
ehci_do_poll(struct usb_bus *bus)
{
ehci_softc_t *sc = EHCI_BUS2SC(bus);
USB_BUS_LOCK(&sc->sc_bus);
ehci_interrupt_poll(sc);
USB_BUS_UNLOCK(&sc->sc_bus);
}
static void
ehci_setup_standard_chain_sub(struct ehci_std_temp *temp)
{
struct usb_page_search buf_res;
ehci_qtd_t *td;
ehci_qtd_t *td_next;
ehci_qtd_t *td_alt_next;
uint32_t buf_offset;
uint32_t average;
uint32_t len_old;
uint32_t terminate;
uint32_t qtd_altnext;
uint8_t shortpkt_old;
uint8_t precompute;
terminate = temp->sc->sc_terminate_self;
qtd_altnext = temp->sc->sc_terminate_self;
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 |
htohc32(temp->sc, EHCI_QTD_IOC |
EHCI_QTD_SET_BYTES(average));
if (average == 0) {
if (temp->auto_data_toggle == 0) {
/* update data toggle, ZLP case */
temp->qtd_status ^=
htohc32(temp->sc, EHCI_QTD_TOGGLE_MASK);
}
td->len = 0;
/* properly reset reserved fields */
td->qtd_buffer[0] = 0;
td->qtd_buffer[1] = 0;
td->qtd_buffer[2] = 0;
td->qtd_buffer[3] = 0;
td->qtd_buffer[4] = 0;
td->qtd_buffer_hi[0] = 0;
td->qtd_buffer_hi[1] = 0;
td->qtd_buffer_hi[2] = 0;
td->qtd_buffer_hi[3] = 0;
td->qtd_buffer_hi[4] = 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 ^=
htohc32(temp->sc, EHCI_QTD_TOGGLE_MASK);
}
}
td->len = average;
/* update remaining length */
temp->len -= average;
/* fill out buffer pointers */
usbd_get_page(temp->pc, buf_offset, &buf_res);
td->qtd_buffer[0] =
htohc32(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;
usbd_get_page(temp->pc, buf_offset, &buf_res);
td->qtd_buffer[x] =
htohc32(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;
usbd_get_page(temp->pc, buf_offset - 1, &buf_res);
td->qtd_buffer[x] =
htohc32(temp->sc,
buf_res.physaddr & (~0xFFF));
td->qtd_buffer_hi[x] = 0;
/* properly reset reserved fields */
while (++x < EHCI_QTD_NBUFFERS) {
td->qtd_buffer[x] = 0;
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;
usb_pc_cpu_flush(td->page_cache);
}
if (precompute) {
precompute = 0;
/* setup alt next pointer, if any */
if (temp->last_frame) {
td_alt_next = NULL;
qtd_altnext = terminate;
} else {
/* we use this field internally */
td_alt_next = td_next;
if (temp->setup_alt_next) {
qtd_altnext = td_next->qtd_self;
} else {
qtd_altnext = terminate;
}
}
/* 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 usb_xfer *xfer, ehci_qh_t **qh_last)
{
struct ehci_std_temp temp;
const struct usb_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->endpointno),
xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
temp.average = xfer->max_hc_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.last_frame = 0;
temp.setup_alt_next = xfer->flags_int.short_frames_ok;
if (xfer->flags_int.control_xfr) {
if (xfer->endpoint->toggle_next) {
/* DATA1 is next */
temp.qtd_status |=
htohc32(temp.sc, EHCI_QTD_SET_TOGGLE(1));
}
temp.auto_data_toggle = 0;
} else {
temp.auto_data_toggle = 1;
}
if ((xfer->xroot->udev->parent_hs_hub != NULL) ||
(xfer->xroot->udev->address != 0)) {
/* max 3 retries */
temp.qtd_status |=
htohc32(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) {
xfer->endpoint->toggle_next = 0;
temp.qtd_status &=
htohc32(temp.sc, EHCI_QTD_SET_CERR(3));
temp.qtd_status |= htohc32(temp.sc,
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;
/* check for last frame */
if (xfer->nframes == 1) {
/* no STATUS stage yet, SETUP is last */
if (xfer->flags_int.control_act) {
temp.last_frame = 1;
temp.setup_alt_next = 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) {
if (xfer->flags_int.control_xfr) {
/* no STATUS stage yet, DATA is last */
if (xfer->flags_int.control_act) {
temp.last_frame = 1;
temp.setup_alt_next = 0;
}
} else {
temp.last_frame = 1;
temp.setup_alt_next = 0;
}
}
/* keep previous data toggle and error count */
temp.qtd_status &=
htohc32(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->endpointno) == UE_DIR_IN) ?
htohc32(temp.sc, EHCI_QTD_ACTIVE |
EHCI_QTD_SET_PID(EHCI_QTD_PID_IN)) :
htohc32(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 &= htohc32(temp.sc, EHCI_QTD_SET_CERR(3) |
EHCI_QTD_SET_TOGGLE(1));
temp.qtd_status |=
(UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) ?
htohc32(temp.sc, EHCI_QTD_ACTIVE |
EHCI_QTD_SET_PID(EHCI_QTD_PID_IN) |
EHCI_QTD_SET_TOGGLE(1)) :
htohc32(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;
temp.last_frame = 1;
temp.setup_alt_next = 0;
ehci_setup_standard_chain_sub(&temp);
}
td = temp.td;
/* the last TD terminates the transfer: */
td->qtd_next = htohc32(temp.sc, EHCI_LINK_TERMINATE);
td->qtd_altnext = htohc32(temp.sc, EHCI_LINK_TERMINATE);
usb_pc_cpu_flush(td->page_cache);
/* must have at least one frame! */
xfer->td_transfer_last = td;
#ifdef USB_DEBUG
if (ehcidebug > 8) {
DPRINTF("nexttog=%d; data before transfer:\n",
xfer->endpoint->toggle_next);
ehci_dump_sqtds(temp.sc,
xfer->td_transfer_first);
}
#endif
methods = xfer->endpoint->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->endpointno)) |
EHCI_QH_SET_MPL(xfer->max_packet_size));
if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) {
qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH);
if (methods != &ehci_device_intr_methods)
qh_endp |= EHCI_QH_SET_NRL(8);
} else {
if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_FULL) {
qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_FULL);
} else {
qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_LOW);
}
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);
}
}
if (temp.auto_data_toggle == 0) {
/* software computes the data toggle */
qh_endp |= EHCI_QH_DTC;
}
qh->qh_endp = htohc32(temp.sc, qh_endp);
qh_endphub =
(EHCI_QH_SET_MULT(xfer->max_packet_count & 3) |
EHCI_QH_SET_CMASK(xfer->endpoint->usb_cmask) |
EHCI_QH_SET_SMASK(xfer->endpoint->usb_smask) |
EHCI_QH_SET_HUBA(xfer->xroot->udev->hs_hub_addr) |
EHCI_QH_SET_PORT(xfer->xroot->udev->hs_port_no));
qh->qh_endphub = htohc32(temp.sc, qh_endphub);
qh->qh_curqtd = 0;
/* fill the overlay qTD */
if (temp.auto_data_toggle && xfer->endpoint->toggle_next) {
/* DATA1 is next */
qh->qh_qtd.qtd_status = htohc32(temp.sc, EHCI_QTD_SET_TOGGLE(1));
} else {
qh->qh_qtd.qtd_status = 0;
}
td = xfer->td_transfer_first;
qh->qh_qtd.qtd_next = td->qtd_self;
qh->qh_qtd.qtd_altnext =
htohc32(temp.sc, EHCI_LINK_TERMINATE);
/* properly reset reserved fields */
qh->qh_qtd.qtd_buffer[0] = 0;
qh->qh_qtd.qtd_buffer[1] = 0;
qh->qh_qtd.qtd_buffer[2] = 0;
qh->qh_qtd.qtd_buffer[3] = 0;
qh->qh_qtd.qtd_buffer[4] = 0;
qh->qh_qtd.qtd_buffer_hi[0] = 0;
qh->qh_qtd.qtd_buffer_hi[1] = 0;
qh->qh_qtd.qtd_buffer_hi[2] = 0;
qh->qh_qtd.qtd_buffer_hi[3] = 0;
qh->qh_qtd.qtd_buffer_hi[4] = 0;
usb_pc_cpu_flush(qh->page_cache);
if (xfer->xroot->udev->flags.self_suspended == 0) {
EHCI_APPEND_QH(qh, *qh_last);
}
}
static void
ehci_root_intr(ehci_softc_t *sc)
{
uint16_t i;
uint16_t m;
USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
/* clear any old interrupt data */
memset(sc->sc_hub_idata, 0, 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);
}
}
uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
sizeof(sc->sc_hub_idata));
}
static void
ehci_isoc_fs_done(ehci_softc_t *sc, struct usb_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 endpoint=%p transfer done\n",
xfer, xfer->endpoint);
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];
}
#ifdef USB_DEBUG
if (ehcidebug > 15) {
DPRINTF("isoc FS-TD\n");
ehci_dump_sitd(sc, td);
}
#endif
usb_pc_cpu_invalidate(td->page_cache);
status = hc32toh(sc, td->sitd_status);
len = EHCI_SITD_GET_LEN(status);
DPRINTFN(2, "status=0x%08x, rem=%u\n", status, len);
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 usb_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 endpoint=%p transfer done\n",
xfer, xfer->endpoint);
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];
}
#ifdef USB_DEBUG
if (ehcidebug > 15) {
DPRINTF("isoc HS-TD\n");
ehci_dump_itd(sc, td);
}
#endif
usb_pc_cpu_invalidate(td->page_cache);
status = hc32toh(sc, td->itd_status[td_no]);
len = EHCI_ITD_GET_LEN(status);
DPRINTFN(2, "status=0x%08x, len=%u\n", status, len);
if (xfer->endpoint->usb_smask & (1 << td_no)) {
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++;
nframes--;
}
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 usb_xfer *xfer, usb_error_t error)
{
const struct usb_pipe_methods *methods = xfer->endpoint->methods;
ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n",
xfer, xfer->endpoint, error);
if ((methods == &ehci_device_bulk_methods) ||
(methods == &ehci_device_ctrl_methods)) {
#ifdef USB_DEBUG
if (ehcidebug > 8) {
DPRINTF("nexttog=%d; data after transfer:\n",
xfer->endpoint->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 */
usbd_transfer_done(xfer, error);
}
/*------------------------------------------------------------------------*
* ehci bulk support
*------------------------------------------------------------------------*/
static void
ehci_device_bulk_open(struct usb_xfer *xfer)
{
return;
}
static void
ehci_device_bulk_close(struct usb_xfer *xfer)
{
ehci_device_done(xfer, USB_ERR_CANCELLED);
}
static void
ehci_device_bulk_enter(struct usb_xfer *xfer)
{
return;
}
static void
ehci_doorbell_async(struct ehci_softc *sc)
{
uint32_t temp;
/*
* XXX Performance quirk: Some Host Controllers have a too low
* interrupt rate. Issue an IAAD to stimulate the Host
* Controller after queueing the BULK transfer.
*
* XXX Force the host controller to refresh any QH caches.
*/
temp = EOREAD4(sc, EHCI_USBCMD);
if (!(temp & EHCI_CMD_IAAD))
EOWRITE4(sc, EHCI_USBCMD, temp | EHCI_CMD_IAAD);
}
static void
ehci_device_bulk_start(struct usb_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);
/*
* XXX Certain nVidia chipsets choke when using the IAAD
* feature too frequently.
*/
if (sc->sc_flags & EHCI_SCFLG_IAADBUG)
return;
ehci_doorbell_async(sc);
}
static const struct usb_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,
};
/*------------------------------------------------------------------------*
* ehci control support
*------------------------------------------------------------------------*/
static void
ehci_device_ctrl_open(struct usb_xfer *xfer)
{
return;
}
static void
ehci_device_ctrl_close(struct usb_xfer *xfer)
{
ehci_device_done(xfer, USB_ERR_CANCELLED);
}
static void
ehci_device_ctrl_enter(struct usb_xfer *xfer)
{
return;
}
static void
ehci_device_ctrl_start(struct usb_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);
}
static const struct usb_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,
};
/*------------------------------------------------------------------------*
* ehci interrupt support
*------------------------------------------------------------------------*/
static void
ehci_device_intr_open(struct usb_xfer *xfer)
{
ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
uint16_t best;
uint16_t bit;
uint16_t x;
usb_hs_bandwidth_alloc(xfer);
/*
* 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 usb_xfer *xfer)
{
ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
sc->sc_intr_stat[xfer->qh_pos]--;
ehci_device_done(xfer, USB_ERR_CANCELLED);
/* bandwidth must be freed after device done */
usb_hs_bandwidth_free(xfer);
}
static void
ehci_device_intr_enter(struct usb_xfer *xfer)
{
return;
}
static void
ehci_device_intr_start(struct usb_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);
}
static const struct usb_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,
};
/*------------------------------------------------------------------------*
* ehci full speed isochronous support
*------------------------------------------------------------------------*/
static void
ehci_device_isoc_fs_open(struct usb_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->endpointno)) |
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->endpointno) == UE_DIR_IN)
sitd_portaddr |= EHCI_SITD_SET_DIR_IN;
sitd_portaddr = htohc32(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 = htohc32(sc, EHCI_LINK_TERMINATE);
usb_pc_cpu_flush(td->page_cache);
}
}
}
static void
ehci_device_isoc_fs_close(struct usb_xfer *xfer)
{
ehci_device_done(xfer, USB_ERR_CANCELLED);
}
static void
ehci_device_isoc_fs_enter(struct usb_xfer *xfer)
{
struct usb_page_search buf_res;
ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
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;
#ifdef USB_DEBUG
uint8_t once = 1;
#endif
DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
xfer, xfer->endpoint->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->endpoint->isoc_next) &
(EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
if ((xfer->endpoint->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->endpoint->isoc_next = (nframes + 3) &
(EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
xfer->endpoint->is_synced = 1;
DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
}
/*
* compute how many milliseconds the insertion is ahead of the
* current frame position:
*/
buf_offset = (xfer->endpoint->isoc_next - nframes) &
(EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
/*
* pre-compute when the isochronous transfer will be finished:
*/
xfer->isoc_time_complete =
usb_isoc_time_expand(&sc->sc_bus, 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->endpoint->isoc_next];
/* store starting position */
xfer->qh_pos = xfer->endpoint->isoc_next;
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];
/* reuse sitd_portaddr and sitd_back from last transfer */
if (*plen > xfer->max_frame_size) {
#ifdef 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;
}
/* allocate a slot */
sa = usbd_fs_isoc_schedule_alloc_slot(xfer,
xfer->isoc_time_complete - nframes - 1);
if (sa == 255) {
/*
* Schedule is FULL, set length to zero:
*/
*plen = 0;
sa = USB_FS_ISOC_UFRAME_MAX - 1;
}
if (*plen) {
/*
* only call "usbd_get_page()" when we have a
* non-zero length
*/
usbd_get_page(xfer->frbuffers, buf_offset, &buf_res);
td->sitd_bp[0] = htohc32(sc, buf_res.physaddr);
buf_offset += *plen;
/*
* NOTE: We need to subtract one from the offset so
* that we are on a valid page!
*/
usbd_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->endpointno) == 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] = htohc32(sc, temp);
td->sitd_mask = htohc32(sc, sitd_mask);
if (nframes == 0) {
td->sitd_status = htohc32(sc,
EHCI_SITD_IOC |
EHCI_SITD_ACTIVE |
EHCI_SITD_SET_LEN(*plen));
} else {
td->sitd_status = htohc32(sc,
EHCI_SITD_ACTIVE |
EHCI_SITD_SET_LEN(*plen));
}
usb_pc_cpu_flush(td->page_cache);
#ifdef 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++;
td_last = td;
td = td->obj_next;
}
xfer->td_transfer_last = td_last;
/* update isoc_next */
xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_fs_p_last[0]) &
(EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
/*
* We don't allow cancelling of the SPLIT transaction USB FULL
* speed transfer, because it disturbs the bandwidth
* computation algorithm.
*/
xfer->flags_int.can_cancel_immed = 0;
}
static void
ehci_device_isoc_fs_start(struct usb_xfer *xfer)
{
/*
* We don't allow cancelling of the SPLIT transaction USB FULL
* speed transfer, because it disturbs the bandwidth
* computation algorithm.
*/
xfer->flags_int.can_cancel_immed = 0;
/* set a default timeout */
if (xfer->timeout == 0)
xfer->timeout = 500; /* ms */
/* put transfer on interrupt queue */
ehci_transfer_intr_enqueue(xfer);
}
static const struct usb_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,
};
/*------------------------------------------------------------------------*
* ehci high speed isochronous support
*------------------------------------------------------------------------*/
static void
ehci_device_isoc_hs_open(struct usb_xfer *xfer)
{
ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
ehci_itd_t *td;
uint32_t temp;
uint8_t ds;
usb_hs_bandwidth_alloc(xfer);
/* 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] = htohc32(sc,
EHCI_ITD_SET_ADDR(xfer->address) |
EHCI_ITD_SET_ENDPT(UE_GET_ADDR(xfer->endpointno)));
temp =
EHCI_ITD_SET_MPL(xfer->max_packet_size & 0x7FF);
/* set direction */
if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) {
temp |= EHCI_ITD_SET_DIR_IN;
}
/* set maximum packet size */
td->itd_bp[1] = htohc32(sc, temp);
/* set transfer multiplier */
td->itd_bp[2] = htohc32(sc, xfer->max_packet_count & 3);
usb_pc_cpu_flush(td->page_cache);
}
}
}
static void
ehci_device_isoc_hs_close(struct usb_xfer *xfer)
{
ehci_device_done(xfer, USB_ERR_CANCELLED);
/* bandwidth must be freed after device done */
usb_hs_bandwidth_free(xfer);
}
static void
ehci_device_isoc_hs_enter(struct usb_xfer *xfer)
{
struct usb_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;
uint8_t shift = usbd_xfer_get_fps_shift(xfer);
#ifdef USB_DEBUG
uint8_t once = 1;
#endif
DPRINTFN(6, "xfer=%p next=%d nframes=%d shift=%d\n",
xfer, xfer->endpoint->isoc_next, xfer->nframes, (int)shift);
/* 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->endpoint->isoc_next) &
(EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
if ((xfer->endpoint->is_synced == 0) ||
(buf_offset < (((xfer->nframes << shift) + 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->endpoint->isoc_next = (nframes + 3) &
(EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
xfer->endpoint->is_synced = 1;
DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
}
/*
* compute how many milliseconds the insertion is ahead of the
* current frame position:
*/
buf_offset = (xfer->endpoint->isoc_next - nframes) &
(EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
/*
* pre-compute when the isochronous transfer will be finished:
*/
xfer->isoc_time_complete =
usb_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset +
(((xfer->nframes << shift) + 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->endpoint->isoc_next];
/* store starting position */
xfer->qh_pos = xfer->endpoint->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) {
#ifdef 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;
}
if (xfer->endpoint->usb_smask & (1 << td_no)) {
status = (EHCI_ITD_SET_LEN(*plen) |
EHCI_ITD_ACTIVE |
EHCI_ITD_SET_PG(0));
td->itd_status[td_no] = htohc32(sc, status);
itd_offset[td_no] = buf_offset;
buf_offset += *plen;
plen++;
nframes --;
} else {
td->itd_status[td_no] = 0; /* not active */
itd_offset[td_no] = buf_offset;
}
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 */
usbd_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] &= htohc32(sc, 0xFFF);
td->itd_bp[0] |= htohc32(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] |= htohc32(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!
*/
usbd_get_page(xfer->frbuffers, buf_offset - 1, &buf_res);
} else {
usbd_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] &= htohc32(sc, 0xFFF);
td->itd_bp[page_no] |= htohc32(sc, page_addr);
}
}
}
/* set IOC bit if we are complete */
if (nframes == 0) {
td->itd_status[td_no - 1] |= htohc32(sc, EHCI_ITD_IOC);
}
usb_pc_cpu_flush(td->page_cache);
#ifdef 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->endpoint->isoc_next = (pp_last - &sc->sc_isoc_hs_p_last[0]) &
(EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
}
static void
ehci_device_isoc_hs_start(struct usb_xfer *xfer)
{
/* put transfer on interrupt queue */
ehci_transfer_intr_enqueue(xfer);
}
static const struct usb_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,
};
/*------------------------------------------------------------------------*
* ehci root control support
*------------------------------------------------------------------------*
* Simulate a hardware hub by handling all the necessary requests.
*------------------------------------------------------------------------*/
static const
struct usb_device_descriptor ehci_devd =
{
sizeof(struct usb_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 usb_device_qualifier ehci_odevd =
{
sizeof(struct usb_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 usb_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 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 | EHCI_INTR_ENDPT,
.bmAttributes = UE_INTERRUPT,
.wMaxPacketSize[0] = 8, /* max packet (63 ports) */
.bInterval = 255,
},
};
static const
struct usb_hub_descriptor ehci_hubd =
{
.bDescLength = 0, /* dynamic length */
.bDescriptorType = UDESC_HUB,
};
+uint16_t
+ehci_get_port_speed_portsc(struct ehci_softc *sc, uint16_t index)
+{
+ uint32_t v;
+
+ v = EOREAD4(sc, EHCI_PORTSC(index));
+ v = (v >> EHCI_PORTSC_PSPD_SHIFT) & EHCI_PORTSC_PSPD_MASK;
+
+ if (v == EHCI_PORT_SPEED_HIGH)
+ return (UPS_HIGH_SPEED);
+ if (v == EHCI_PORT_SPEED_LOW)
+ return (UPS_LOW_SPEED);
+ return (0);
+}
+
+uint16_t
+ehci_get_port_speed_hostc(struct ehci_softc *sc, uint16_t index)
+{
+ uint32_t v;
+
+ v = EOREAD4(sc, EHCI_HOSTC(index));
+ v = (v >> EHCI_HOSTC_PSPD_SHIFT) & EHCI_HOSTC_PSPD_MASK;
+
+ if (v == EHCI_PORT_SPEED_HIGH)
+ return (UPS_HIGH_SPEED);
+ if (v == EHCI_PORT_SPEED_LOW)
+ return (UPS_LOW_SPEED);
+ return (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 usb_error_t
ehci_roothub_exec(struct usb_device *udev,
struct usb_device_request *req, const void **pptr, uint16_t *plength)
{
ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
const char *str_ptr;
const void *ptr;
uint32_t port;
uint32_t v;
uint16_t len;
uint16_t i;
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_desc;
len = 0;
err = 0;
value = UGETW(req->wValue);
index = UGETW(req->wIndex);
DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x "
"wValue=0x%04x wIndex=0x%04x\n",
req->bmRequestType, req->bRequest,
UGETW(req->wLength), value, index);
#define C(x,y) ((x) | ((y) << 8))
switch (C(req->bRequest, 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):
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) {
err = USB_ERR_IOERROR;
goto done;
}
len = sizeof(ehci_devd);
ptr = (const void *)&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) {
err = USB_ERR_IOERROR;
goto done;
}
len = sizeof(ehci_odevd);
ptr = (const void *)&ehci_odevd;
break;
case UDESC_CONFIG:
if ((value & 0xff) != 0) {
err = USB_ERR_IOERROR;
goto done;
}
len = sizeof(ehci_confd);
ptr = (const void *)&ehci_confd;
break;
case UDESC_STRING:
switch (value & 0xff) {
case 0: /* Language table */
str_ptr = "\001";
break;
case 1: /* Vendor */
str_ptr = sc->sc_vendor;
break;
case 2: /* Product */
str_ptr = "EHCI root HUB";
break;
default:
str_ptr = "";
break;
}
len = usb_make_str_desc(
sc->sc_hub_desc.temp,
sizeof(sc->sc_hub_desc.temp),
str_ptr);
break;
default:
err = USB_ERR_IOERROR;
goto done;
}
break;
case C(UR_GET_INTERFACE, UT_READ_INTERFACE):
len = 1;
sc->sc_hub_desc.temp[0] = 0;
break;
case C(UR_GET_STATUS, UT_READ_DEVICE):
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):
len = 2;
USETW(sc->sc_hub_desc.stat.wStatus, 0);
break;
case C(UR_SET_ADDRESS, UT_WRITE_DEVICE):
if (value >= EHCI_MAX_DEVICES) {
err = USB_ERR_IOERROR;
goto done;
}
sc->sc_addr = value;
break;
case C(UR_SET_CONFIG, UT_WRITE_DEVICE):
if ((value != 0) && (value != 1)) {
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):
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)) {
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 */
usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50);
EOWRITE4(sc, port, v & ~(EHCI_PS_SUSP |
EHCI_PS_FPR | (3 << 10) /* High Speed */ ));
/* 4ms settle time */
usb_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:
err = USB_ERR_IOERROR;
goto done;
}
break;
case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
if ((value & 0xff) != 0) {
err = USB_ERR_IOERROR;
goto done;
}
v = EREAD4(sc, EHCI_HCSPARAMS);
sc->sc_hub_desc.hubd = ehci_hubd;
sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport;
if (EHCI_HCS_PPC(v))
i = UHD_PWR_INDIVIDUAL;
else
i = UHD_PWR_NO_SWITCH;
if (EHCI_HCS_P_INDICATOR(v))
i |= UHD_PORT_IND;
USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, i);
/* XXX can't find out? */
sc->sc_hub_desc.hubd.bPwrOn2PwrGood = 200;
/* XXX don't know if ports are removable or not */
sc->sc_hub_desc.hubd.bDescLength =
8 + ((sc->sc_noport + 7) / 8);
len = sc->sc_hub_desc.hubd.bDescLength;
break;
case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE):
len = 16;
memset(sc->sc_hub_desc.temp, 0, 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)) {
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 | EHCI_SCFLG_TT)) {
- if ((v & 0xc000000) == 0x8000000)
+ if (sc->sc_flags & EHCI_SCFLG_TT) {
+ if (sc->sc_vendor_get_port_speed != NULL) {
+ i = sc->sc_vendor_get_port_speed(sc, index);
+ } else {
+ device_printf(sc->sc_bus.bdev,
+ "EHCI_SCFLG_TT quirk is set but "
+ "sc_vendor_get_hub_speed() is NULL\n");
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);
len = sizeof(sc->sc_hub_desc.ps);
break;
case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
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)) {
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);
#ifdef USB_DEBUG
if (ehcinohighspeed) {
/*
* Connect USB device to companion
* controller.
*/
ehci_disown(sc, index, 1);
break;
}
#endif
if (EHCI_PS_IS_LOWSPEED(v) &&
(sc->sc_flags & EHCI_SCFLG_TT) == 0) {
/* 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);
/* Wait for reset to complete. */
usb_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);
/* Wait for HC to complete reset. */
usb_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");
err = USB_ERR_TIMEOUT;
goto done;
}
if (!(v & EHCI_PS_PE) &&
(sc->sc_flags & EHCI_SCFLG_TT) == 0) {
/* 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:
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:
err = USB_ERR_IOERROR;
goto done;
}
done:
*plength = len;
*pptr = ptr;
return (err);
}
static void
ehci_xfer_setup(struct usb_setup_params *parm)
{
struct usb_page_search page_info;
struct usb_page_cache *pc;
ehci_softc_t *sc;
struct usb_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;
usbd_transfer_setup_sub(parm);
nqh = 1;
nqtd = ((2 * xfer->nframes) + 1 /* STATUS */
+ (xfer->max_data_length / xfer->max_hc_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;
usbd_transfer_setup_sub(parm);
nqh = 1;
nqtd = ((2 * xfer->nframes)
+ (xfer->max_data_length / xfer->max_hc_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;
usbd_transfer_setup_sub(parm);
nqh = 1;
nqtd = ((2 * xfer->nframes)
+ (xfer->max_data_length / xfer->max_hc_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;
usbd_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;
usbd_transfer_setup_sub(parm);
nitd = ((xfer->nframes + 7) / 8) <<
usbd_xfer_get_fps_shift(xfer);
} else {
parm->hc_max_packet_size = 0x400;
parm->hc_max_packet_count = 1;
parm->hc_max_frame_size = 0x400;
usbd_transfer_setup_sub(parm);
}
alloc_dma_set:
if (parm->err) {
return;
}
/*
* Allocate queue heads and transfer descriptors
*/
last_obj = NULL;
if (usbd_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;
usbd_get_page(pc + n, 0, &page_info);
td = page_info.buffer;
/* init TD */
td->itd_self = htohc32(sc, page_info.physaddr | EHCI_LINK_ITD);
td->obj_next = last_obj;
td->page_cache = pc + n;
last_obj = td;
usb_pc_cpu_flush(pc + n);
}
}
if (usbd_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;
usbd_get_page(pc + n, 0, &page_info);
td = page_info.buffer;
/* init TD */
td->sitd_self = htohc32(sc, page_info.physaddr | EHCI_LINK_SITD);
td->obj_next = last_obj;
td->page_cache = pc + n;
last_obj = td;
usb_pc_cpu_flush(pc + n);
}
}
if (usbd_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;
usbd_get_page(pc + n, 0, &page_info);
qtd = page_info.buffer;
/* init TD */
qtd->qtd_self = htohc32(sc, page_info.physaddr);
qtd->obj_next = last_obj;
qtd->page_cache = pc + n;
last_obj = qtd;
usb_pc_cpu_flush(pc + n);
}
}
xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj;
last_obj = NULL;
if (usbd_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;
usbd_get_page(pc + n, 0, &page_info);
qh = page_info.buffer;
/* init QH */
qh->qh_self = htohc32(sc, page_info.physaddr | EHCI_LINK_QH);
qh->obj_next = last_obj;
qh->page_cache = pc + n;
last_obj = qh;
usb_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 usb_xfer *xfer)
{
return;
}
static void
ehci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
struct usb_endpoint *ep)
{
ehci_softc_t *sc = EHCI_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_addr);
if (udev->device_index != sc->sc_addr) {
if ((udev->speed != USB_SPEED_HIGH) &&
((udev->hs_hub_addr == 0) ||
(udev->hs_port_no == 0) ||
(udev->parent_hs_hub == NULL) ||
(udev->parent_hs_hub->hub == NULL))) {
/* We need a transaction translator */
goto done;
}
switch (edesc->bmAttributes & UE_XFERTYPE) {
case UE_CONTROL:
ep->methods = &ehci_device_ctrl_methods;
break;
case UE_INTERRUPT:
ep->methods = &ehci_device_intr_methods;
break;
case UE_ISOCHRONOUS:
if (udev->speed == USB_SPEED_HIGH) {
ep->methods = &ehci_device_isoc_hs_methods;
} else if (udev->speed == USB_SPEED_FULL) {
ep->methods = &ehci_device_isoc_fs_methods;
}
break;
case UE_BULK:
ep->methods = &ehci_device_bulk_methods;
break;
default:
/* do nothing */
break;
}
}
done:
return;
}
static void
ehci_get_dma_delay(struct usb_device *udev, uint32_t *pus)
{
/*
* Wait until the hardware has finished any possible use of
* the transfer descriptor(s) and QH
*/
*pus = (1125); /* microseconds */
}
static void
ehci_device_resume(struct usb_device *udev)
{
ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
struct usb_xfer *xfer;
const struct usb_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->endpoint->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 usb_device *udev)
{
ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
struct usb_xfer *xfer;
const struct usb_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->endpoint->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);
}
static void
ehci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
{
struct ehci_softc *sc = EHCI_BUS2SC(bus);
switch (state) {
case USB_HW_POWER_SUSPEND:
case USB_HW_POWER_SHUTDOWN:
ehci_suspend(sc);
break;
case USB_HW_POWER_RESUME:
ehci_resume(sc);
break;
default:
break;
}
}
static void
ehci_set_hw_power(struct usb_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;
}
static void
ehci_start_dma_delay_second(struct usb_xfer *xfer)
{
struct ehci_softc *sc = EHCI_BUS2SC(xfer->xroot->bus);
DPRINTF("\n");
/* trigger doorbell */
ehci_doorbell_async(sc);
/* give the doorbell 4ms */
usbd_transfer_timeout_ms(xfer,
(void (*)(void *))&usb_dma_delay_done_cb, 4);
}
/*
* Ring the doorbell twice before freeing any DMA descriptors. Some host
* controllers apparently cache the QH descriptors and need a message
* that the cache needs to be discarded.
*/
static void
ehci_start_dma_delay(struct usb_xfer *xfer)
{
struct ehci_softc *sc = EHCI_BUS2SC(xfer->xroot->bus);
DPRINTF("\n");
/* trigger doorbell */
ehci_doorbell_async(sc);
/* give the doorbell 4ms */
usbd_transfer_timeout_ms(xfer,
(void (*)(void *))&ehci_start_dma_delay_second, 4);
}
static const struct usb_bus_methods ehci_bus_methods =
{
.endpoint_init = ehci_ep_init,
.xfer_setup = ehci_xfer_setup,
.xfer_unsetup = ehci_xfer_unsetup,
.get_dma_delay = ehci_get_dma_delay,
.device_resume = ehci_device_resume,
.device_suspend = ehci_device_suspend,
.set_hw_power = ehci_set_hw_power,
.set_hw_power_sleep = ehci_set_hw_power_sleep,
.roothub_exec = ehci_roothub_exec,
.xfer_poll = ehci_do_poll,
.start_dma_delay = ehci_start_dma_delay,
};
Index: head/sys/dev/usb/controller/ehci.h
===================================================================
--- head/sys/dev/usb/controller/ehci.h (revision 294988)
+++ head/sys/dev/usb/controller/ehci.h (revision 294989)
@@ -1,450 +1,453 @@
/* $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.
*
* 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 MIN(USB_MAX_DEVICES, 128)
/*
* 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 (1U << 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 usb_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 (1U << 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 usb_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 usb_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 usb_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 usb_page_cache pframes_pc;
struct usb_page_cache terminate_pc;
struct usb_page_cache async_start_pc;
struct usb_page_cache intr_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT];
struct usb_page_cache isoc_hs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT];
struct usb_page_cache isoc_fs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT];
struct usb_page pframes_pg;
struct usb_page terminate_pg;
struct usb_page async_start_pg;
struct usb_page intr_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT];
struct usb_page isoc_hs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT];
struct usb_page isoc_fs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT];
};
struct ehci_config_desc {
struct usb_config_descriptor confd;
struct usb_interface_descriptor ifcd;
struct usb_endpoint_descriptor endpd;
} __packed;
union ehci_hub_desc {
struct usb_status stat;
struct usb_port_status ps;
struct usb_hub_descriptor hubd;
uint8_t temp[128];
};
typedef struct ehci_softc {
struct ehci_hw_softc sc_hw;
struct usb_bus sc_bus; /* base device */
struct usb_callout sc_tmo_pcd;
struct usb_callout sc_tmo_poll;
union ehci_hub_desc sc_hub_desc;
struct usb_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_terminate_self; /* TD short packet termination pointer */
uint32_t sc_eintrs;
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 */
#define EHCI_SCFLG_LOSTINTRBUG 0x0040 /* workaround for VIA / ATI chipsets */
#define EHCI_SCFLG_IAADBUG 0x0080 /* workaround for nVidia chipsets */
#define EHCI_SCFLG_DONTRESET 0x0100 /* don't reset ctrl. in ehci_init() */
#define EHCI_SCFLG_DONEINIT 0x1000 /* ehci_init() has been called. */
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 */
+ void (*sc_vendor_post_reset)(struct ehci_softc *sc);
+ uint16_t (*sc_vendor_get_port_speed)(struct ehci_softc *sc,
+ uint16_t index);
+
} 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))
#ifdef USB_EHCI_BIG_ENDIAN_DESC
/*
* Handle byte order conversion between host and ``host controller''.
* Typically the latter is little-endian but some controllers require
* big-endian in which case we may need to manually swap.
*/
static __inline uint32_t
htohc32(const struct ehci_softc *sc, const uint32_t v)
{
return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? htobe32(v) : htole32(v);
}
static __inline uint16_t
htohc16(const struct ehci_softc *sc, const uint16_t v)
{
return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? htobe16(v) : htole16(v);
}
static __inline uint32_t
hc32toh(const struct ehci_softc *sc, const uint32_t v)
{
return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? be32toh(v) : le32toh(v);
}
static __inline uint16_t
hc16toh(const struct ehci_softc *sc, const uint16_t v)
{
return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? be16toh(v) : le16toh(v);
}
#else
/*
* Normal little-endian only conversion routines.
*/
static __inline uint32_t
htohc32(const struct ehci_softc *sc, const uint32_t v)
{
return htole32(v);
}
static __inline uint16_t
htohc16(const struct ehci_softc *sc, const uint16_t v)
{
return htole16(v);
}
static __inline uint32_t
hc32toh(const struct ehci_softc *sc, const uint32_t v)
{
return le32toh(v);
}
static __inline uint16_t
hc16toh(const struct ehci_softc *sc, const uint16_t v)
{
return le16toh(v);
}
#endif
usb_bus_mem_cb_t ehci_iterate_hw_softc;
usb_error_t ehci_reset(ehci_softc_t *sc);
usb_error_t ehci_init(ehci_softc_t *sc);
void ehci_detach(struct ehci_softc *sc);
void ehci_interrupt(ehci_softc_t *sc);
+uint16_t ehci_get_port_speed_portsc(struct ehci_softc *sc, uint16_t index);
+uint16_t ehci_get_port_speed_hostc(struct ehci_softc *sc, uint16_t index);
#endif /* _EHCI_H_ */
Index: head/sys/dev/usb/controller/ehci_ixp4xx.c
===================================================================
--- head/sys/dev/usb/controller/ehci_ixp4xx.c (revision 294988)
+++ head/sys/dev/usb/controller/ehci_ixp4xx.c (revision 294989)
@@ -1,313 +1,327 @@
/*-
* 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
__FBSDID("$FreeBSD$");
#include "opt_bus.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 uint8_t ehci_bs_r_1(bus_space_tag_t tag, bus_space_handle_t, bus_size_t);
static void ehci_bs_w_1(bus_space_tag_t tag, bus_space_handle_t, bus_size_t, u_int8_t);
static uint16_t ehci_bs_r_2(bus_space_tag_t tag, bus_space_handle_t, bus_size_t);
static void ehci_bs_w_2(bus_space_tag_t tag, bus_space_handle_t, bus_size_t, uint16_t);
static uint32_t ehci_bs_r_4(bus_space_tag_t tag, bus_space_handle_t, bus_size_t);
static void ehci_bs_w_4(bus_space_tag_t tag, bus_space_handle_t, bus_size_t, uint32_t);
+static void
+ehci_ixp_post_reset(struct ehci_softc *ehci_softc)
+{
+ uint32_t usbmode;
+
+ /* Force HOST mode, select big-endian mode */
+ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_NOLPM);
+ usbmode &= ~EHCI_UM_CM;
+ usbmode |= EHCI_UM_CM_HOST;
+ usbmode |= EHCI_UM_ES_BE;
+ EOWRITE4(ehci_softc, EHCI_USBMODE_NOLPM, usbmode);
+}
+
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;
sc->sc_bus.dma_bits = 32;
/* get all DMA memory */
if (usb_bus_mem_alloc_all(&sc->sc_bus,
USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) {
return (ENOMEM);
}
/* 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_privdata = 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, (driver_intr_t *)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.
+ * 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
;
+
+ /* Setup callbacks. */
+ sc->sc_vendor_post_reset = ehci_ixp_post_reset;
+ sc->sc_vendor_get_port_speed = ehci_get_port_speed_portsc;
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_children(self);
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;
}
usb_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(bus_space_tag_t tag, bus_space_handle_t h, bus_size_t o)
{
return bus_space_read_1((bus_space_tag_t)tag->bs_privdata, h,
0x100 + (o &~ 3) + (3 - (o & 3)));
}
static void
ehci_bs_w_1(bus_space_tag_t tag, bus_space_handle_t h, bus_size_t o, u_int8_t v)
{
panic("%s", __func__);
}
static uint16_t
ehci_bs_r_2(bus_space_tag_t tag, bus_space_handle_t h, bus_size_t o)
{
return bus_space_read_2((bus_space_tag_t)tag->bs_privdata, h,
0x100 + (o &~ 3) + (2 - (o & 3)));
}
static void
ehci_bs_w_2(bus_space_tag_t tag, bus_space_handle_t h, bus_size_t o, uint16_t v)
{
panic("%s", __func__);
}
static uint32_t
ehci_bs_r_4(bus_space_tag_t tag, bus_space_handle_t h, bus_size_t o)
{
return bus_space_read_4((bus_space_tag_t) tag->bs_privdata, h, 0x100 + o);
}
static void
ehci_bs_w_4(bus_space_tag_t tag, bus_space_handle_t h, bus_size_t o, uint32_t v)
{
bus_space_write_4((bus_space_tag_t) tag->bs_privdata, 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, bus_generic_suspend),
DEVMETHOD(device_resume, bus_generic_resume),
DEVMETHOD(device_shutdown, bus_generic_shutdown),
DEVMETHOD_END
};
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);
Index: head/sys/dev/usb/controller/ehci_mv.c
===================================================================
--- head/sys/dev/usb/controller/ehci_mv.c (revision 294988)
+++ head/sys/dev/usb/controller/ehci_mv.c (revision 294989)
@@ -1,363 +1,375 @@
/*-
* 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.
*/
/*
* FDT attachment driver for the USB Enhanced Host Controller.
*/
#include
__FBSDID("$FreeBSD$");
#include "opt_bus.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define EHCI_VENDORID_MRVL 0x1286
#define EHCI_HC_DEVSTR "Marvell Integrated USB 2.0 controller"
static device_attach_t mv_ehci_attach;
static device_detach_t mv_ehci_detach;
static int err_intr(void *arg);
static struct resource *irq_err;
static void *ih_err;
/* EHCI HC regs start at this offset within USB range */
#define MV_USB_HOST_OFST 0x0100
#define USB_BRIDGE_INTR_CAUSE 0x210
#define USB_BRIDGE_INTR_MASK 0x214
#define USB_BRIDGE_ERR_ADDR 0x21C
#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 struct ofw_compat_data compat_data[] = {
{"mrvl,usb-ehci", true},
{"marvell,orion-ehci", true},
{NULL, false}
};
+static void
+mv_ehci_post_reset(struct ehci_softc *ehci_softc)
+{
+ uint32_t usbmode;
+
+ /* Force HOST mode */
+ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_NOLPM);
+ usbmode &= ~EHCI_UM_CM;
+ usbmode |= EHCI_UM_CM_HOST;
+ EOWRITE4(ehci_softc, EHCI_USBMODE_NOLPM, usbmode);
+}
+
static int
mv_ehci_probe(device_t self)
{
if (!ofw_bus_status_okay(self))
return (ENXIO);
if (!ofw_bus_search_compatible(self, compat_data)->ocd_data)
return (ENXIO);
device_set_desc(self, EHCI_HC_DEVSTR);
return (BUS_PROBE_DEFAULT);
}
static int
mv_ehci_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;
sc->sc_bus.dma_bits = 32;
/* get all DMA memory */
if (usb_bus_mem_alloc_all(&sc->sc_bus,
USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) {
return (ENOMEM);
}
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 = rman_get_size(sc->sc_io_res) - 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;
if (!ofw_bus_is_compatible(self, "marvell,orion-ehci")) {
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");
mv_ehci_detach(self);
return (ENXIO);
}
rid = 1;
}
/*
* Notice: Marvell EHCI controller has TWO interrupt lines, so make
* sure to use the correct rid for the main one (controller interrupt)
* -- refer to DTS for the right resource number to use here.
*/
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");
if (!ofw_bus_is_compatible(self, "marvell,orion-ehci")) {
err = bus_setup_intr(self, irq_err, 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, (driver_intr_t *)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;
+ sc->sc_vendor_post_reset = mv_ehci_post_reset;
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;
-
+ sc->sc_flags |= EHCI_SCFLG_TT | EHCI_SCFLG_NORESTERM;
+ sc->sc_vendor_get_port_speed = ehci_get_port_speed_portsc;
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:
mv_ehci_detach(self);
return (ENXIO);
}
static int
mv_ehci_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_children(self);
/*
* disable interrupts that might have been switched on in mv_ehci_attach
*/
if (sc->sc_io_res) {
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;
}
usb_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("USB error: ");
if (cause & MV_USB_ADDR_DECODE_ERR) {
uint32_t addr;
addr = EREAD4(sc, USB_BRIDGE_ERR_ADDR);
printf("address decoding error (addr=%#x)\n", addr);
}
if (cause & MV_USB_HOST_UNDERFLOW)
printf("host underflow\n");
if (cause & MV_USB_HOST_OVERFLOW)
printf("host overflow\n");
if (cause & MV_USB_DEVICE_UNDERFLOW)
printf("device underflow\n");
if (cause & ~(MV_USB_ADDR_DECODE_ERR | MV_USB_HOST_UNDERFLOW |
MV_USB_HOST_OVERFLOW | MV_USB_DEVICE_UNDERFLOW))
printf("unknown cause (cause=%#x)\n", cause);
EWRITE4(sc, USB_BRIDGE_INTR_CAUSE, 0);
}
return (FILTER_HANDLED);
}
static device_method_t ehci_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, mv_ehci_probe),
DEVMETHOD(device_attach, mv_ehci_attach),
DEVMETHOD(device_detach, mv_ehci_detach),
DEVMETHOD(device_suspend, bus_generic_suspend),
DEVMETHOD(device_resume, bus_generic_resume),
DEVMETHOD(device_shutdown, bus_generic_shutdown),
DEVMETHOD_END
};
static driver_t ehci_driver = {
"ehci",
ehci_methods,
sizeof(ehci_softc_t),
};
static devclass_t ehci_devclass;
DRIVER_MODULE(ehci, simplebus, ehci_driver, ehci_devclass, 0, 0);
MODULE_DEPEND(ehci, usb, 1, 1, 1);
Index: head/sys/dev/usb/controller/ehcireg.h
===================================================================
--- head/sys/dev/usb/controller/ehcireg.h (revision 294988)
+++ head/sys/dev/usb/controller/ehcireg.h (revision 294989)
@@ -1,171 +1,193 @@
/* $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.
*
* 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 _EHCIREG_H_
#define _EHCIREG_H_
/* 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_CAPLEN_HCIVERSION 0x00 /* RO Capability register length
* (least-significant byte) and
* interface version number (two
* most significant)
*/
#define EHCI_CAPLENGTH(x) ((x) & 0xff)
#define EHCI_HCIVERSION(x) (((x) >> 16) & 0xffff)
#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_PORT_RESET_COMPLETE 2 /* ms */
+
+/*
+ * Registers not covered by EHCI specification
+ *
+ *
+ * EHCI_USBMODE register offset is different for cores with LPM support,
+ * bits are equal
+ */
+#define EHCI_USBMODE_NOLPM 0x68 /* RW USB Device mode reg (no LPM) */
+#define EHCI_USBMODE_LPM 0xA8 /* RW USB Device mode reg (LPM) */
#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 */
+/*
+ * Actual port speed bits depends on EHCI_HOSTC(n) registers presence,
+ * speed encoding is equal
+ */
+#define EHCI_HOSTC(n) (0x80+(4*(n))) /* RO, RW Host mode control reg */
+#define EHCI_HOSTC_PSPD_SHIFT 25
+#define EHCI_HOSTC_PSPD_MASK 0x3
+#define EHCI_PORTSC_PSPD_SHIFT 26
+#define EHCI_PORTSC_PSPD_MASK 0x3
+
+#define EHCI_PORT_SPEED_FULL 0
+#define EHCI_PORT_SPEED_LOW 1
+#define EHCI_PORT_SPEED_HIGH 2
#endif /* _EHCIREG_H_ */
Index: head/sys/mips/atheros/ar71xx_ehci.c
===================================================================
--- head/sys/mips/atheros/ar71xx_ehci.c (revision 294988)
+++ head/sys/mips/atheros/ar71xx_ehci.c (revision 294989)
@@ -1,278 +1,297 @@
/*-
* 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.
*/
/*
* AR71XX attachment driver for the USB Enhanced Host Controller.
*/
#include
__FBSDID("$FreeBSD$");
#include "opt_bus.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /* for stuff in ar71xx_cpudef.h */
#include
#include
#define EHCI_HC_DEVSTR "AR71XX Integrated USB 2.0 controller"
+#define EHCI_USBMODE 0x68 /* USB Device mode register */
+#define EHCI_UM_CM 0x00000003 /* R/WO Controller Mode */
+#define EHCI_UM_CM_HOST 0x3 /* Host Controller */
+
struct ar71xx_ehci_softc {
ehci_softc_t base; /* storage for EHCI code */
};
static device_attach_t ar71xx_ehci_attach;
static device_detach_t ar71xx_ehci_detach;
bs_r_1_proto(reversed);
bs_w_1_proto(reversed);
+static void
+ar71xx_ehci_post_reset(struct ehci_softc *ehci_softc)
+{
+ uint32_t usbmode;
+
+ /* Force HOST mode */
+ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_NOLPM);
+ usbmode &= ~EHCI_UM_CM;
+ usbmode |= EHCI_UM_CM_HOST;
+ EOWRITE4(ehci_softc, EHCI_USBMODE_NOLPM, usbmode);
+}
+
static int
ar71xx_ehci_probe(device_t self)
{
device_set_desc(self, EHCI_HC_DEVSTR);
return (BUS_PROBE_NOWILDCARD);
}
static void
ar71xx_ehci_intr(void *arg)
{
/* XXX TODO: should really see if this was our interrupt.. */
ar71xx_device_flush_ddr(AR71XX_CPU_DDR_FLUSH_USB);
ehci_interrupt(arg);
}
static int
ar71xx_ehci_attach(device_t self)
{
struct ar71xx_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;
sc->sc_bus.dma_bits = 32;
/* get all DMA memory */
if (usb_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.
*/
sc->sc_io_tag = ar71xx_bus_space_reversed;
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_ACTIVE | RF_SHAREABLE);
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, "Atheros");
err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
NULL, ar71xx_ehci_intr, 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_SETMODE;
+ sc->sc_flags = 0;
+ sc->sc_vendor_post_reset = ar71xx_ehci_post_reset;
switch (ar71xx_soc) {
case AR71XX_SOC_AR7241:
case AR71XX_SOC_AR7242:
case AR71XX_SOC_AR9130:
case AR71XX_SOC_AR9132:
case AR71XX_SOC_AR9330:
case AR71XX_SOC_AR9331:
case AR71XX_SOC_AR9341:
case AR71XX_SOC_AR9342:
case AR71XX_SOC_AR9344:
case AR71XX_SOC_QCA9533:
case AR71XX_SOC_QCA9533_V2:
case AR71XX_SOC_QCA9556:
case AR71XX_SOC_QCA9558:
sc->sc_flags |= EHCI_SCFLG_TT | EHCI_SCFLG_NORESTERM;
+ sc->sc_vendor_get_port_speed =
+ ehci_get_port_speed_portsc;
break;
default:
/* fallthrough */
break;
}
/*
* ehci_reset() needs the correct offset to access the host controller
* registers. The AR724x/AR913x offsets aren't 0.
*/
sc->sc_offs = EHCI_CAPLENGTH(EREAD4(sc, EHCI_CAPLEN_HCIVERSION));
(void) ehci_reset(sc);
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:
ar71xx_ehci_detach(self);
return (ENXIO);
}
static int
ar71xx_ehci_detach(device_t self)
{
struct ar71xx_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_children(self);
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;
}
usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc);
return (0);
}
static device_method_t ehci_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, ar71xx_ehci_probe),
DEVMETHOD(device_attach, ar71xx_ehci_attach),
DEVMETHOD(device_detach, ar71xx_ehci_detach),
DEVMETHOD(device_suspend, bus_generic_suspend),
DEVMETHOD(device_resume, bus_generic_resume),
DEVMETHOD(device_shutdown, bus_generic_shutdown),
DEVMETHOD_END
};
static driver_t ehci_driver = {
.name = "ehci",
.methods = ehci_methods,
.size = sizeof(struct ar71xx_ehci_softc),
};
static devclass_t ehci_devclass;
DRIVER_MODULE(ehci, nexus, ehci_driver, ehci_devclass, 0, 0);
DRIVER_MODULE(ehci, apb, ehci_driver, ehci_devclass, 0, 0);
MODULE_DEPEND(ehci, usb, 1, 1, 1);
Index: head/sys/powerpc/ps3/ehci_ps3.c
===================================================================
--- head/sys/powerpc/ps3/ehci_ps3.c (revision 294988)
+++ head/sys/powerpc/ps3/ehci_ps3.c (revision 294989)
@@ -1,173 +1,184 @@
/*-
* Copyright (C) 2010 Nathan Whitehorn
* 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 TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $FreeBSD$
*/
#include
__FBSDID("$FreeBSD$");
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "ps3bus.h"
struct ps3_ehci_softc {
ehci_softc_t base;
struct bus_space tag;
};
+static void
+ehci_ps3_post_reset(struct ehci_softc *ehci_softc)
+{
+ uint32_t usbmode;
+
+ /* Select big-endian mode */
+ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_NOLPM);
+ usbmode |= EHCI_UM_ES_BE;
+ EOWRITE4(ehci_softc, EHCI_USBMODE_NOLPM, usbmode);
+}
+
static int
ehci_ps3_probe(device_t dev)
{
if (ps3bus_get_bustype(dev) != PS3_BUSTYPE_SYSBUS ||
ps3bus_get_devtype(dev) != PS3_DEVTYPE_USB)
return (ENXIO);
device_set_desc(dev, "Playstation 3 USB 2.0 controller");
return (BUS_PROBE_SPECIFIC);
}
static int
ehci_ps3_attach(device_t dev)
{
ehci_softc_t *sc = device_get_softc(dev);
int rid, err;
sc->sc_bus.parent = dev;
sc->sc_bus.devices = sc->sc_devices;
sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
sc->sc_bus.dma_bits = 32;
/* get all DMA memory */
if (usb_bus_mem_alloc_all(&sc->sc_bus,
USB_GET_DMA_TAG(dev), &ehci_iterate_hw_softc))
return (ENOMEM);
rid = 1;
sc->sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&rid, RF_ACTIVE);
if (!sc->sc_io_res) {
device_printf(dev, "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 = 1;
sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
RF_SHAREABLE | RF_ACTIVE);
if (sc->sc_irq_res == NULL) {
device_printf(dev, "Could not allocate irq\n");
return (ENXIO);
}
sc->sc_bus.bdev = device_add_child(dev, "usbus", -1);
if (!sc->sc_bus.bdev) {
device_printf(dev, "Could not add USB device\n");
return (ENXIO);
}
device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
sprintf(sc->sc_vendor, "Sony");
err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl);
if (err) {
device_printf(dev, "Could not setup error irq, %d\n", err);
goto error;
}
- sc->sc_flags |= EHCI_SCFLG_BIGEMMIO;
+ sc->sc_vendor_post_reset = ehci_ps3_post_reset;
err = ehci_init(sc);
if (err) {
device_printf(dev, "USB init failed err=%d\n", err);
goto error;
}
err = device_probe_and_attach(sc->sc_bus.bdev);
if (err == 0)
return (0);
error:
return (ENXIO);
}
static device_method_t ehci_ps3_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, ehci_ps3_probe),
DEVMETHOD(device_attach, ehci_ps3_attach),
DEVMETHOD(device_resume, bus_generic_resume),
DEVMETHOD(device_suspend, bus_generic_suspend),
DEVMETHOD(device_shutdown, bus_generic_shutdown),
DEVMETHOD_END
};
static driver_t ehci_ps3_driver = {
.name = "ehci",
.methods = ehci_ps3_methods,
.size = sizeof(ehci_softc_t),
};
static devclass_t ehci_ps3_devclass;
DRIVER_MODULE(ehci_ps3, ps3bus, ehci_ps3_driver, ehci_ps3_devclass, 0, 0);
MODULE_DEPEND(ehci_ps3, usb, 1, 1, 1);